public void GetProxyType_IfAlreadyInCache_AlsoAddedToVisited_FromError() { // Arrange var targetType = typeof(IPerson); var sourceType = typeof(Person); var key = new Tuple <Type, Type>(sourceType, targetType); var cache = new ProxyTypeCache(); cache[key] = ProxyTypeCacheResult.FromError(key, "Test Error"); var context = new ProxyTypeEmitter.ProxyBuilderContext(cache, targetType, sourceType); // Act var result = ProxyTypeEmitter.VerifyProxySupport(context, key); // Assert Assert.False(result); Assert.Equal(key, context.Visited.Single().Key); }
public void GetProxyType_IfAlreadyInCache_AlsoAddedToVisited_FromType() { // Arrange var targetType = typeof(IPerson); var sourceType = typeof(Person); var key = new Tuple <Type, Type>(sourceType, targetType); var cache = new ProxyTypeCache(); cache[key] = ProxyTypeCacheResult.FromType(key, sourceType, sourceType.GetConstructor(Array.Empty <Type>())); var context = new ProxyTypeEmitter.ProxyBuilderContext(cache, targetType, sourceType); // Act var result = ProxyTypeEmitter.VerifyProxySupport(context, key); var result2 = ProxyTypeEmitter.VerifyProxySupport(context, key); // Assert Assert.True(result); Assert.True(result2); Assert.Single(context.Visited); Assert.Equal(key, context.Visited.Single().Key); }
private static bool VerifyProxySupport(ProxyBuilderContext context, Tuple <Type, Type> key) { var sourceType = key.Item1; var targetType = key.Item2; if (context.Visited.ContainsKey(key)) { // We've already seen this combination and so far so good. return(true); } ProxyTypeCacheResult cacheResult; if (context.Cache.TryGetValue(key, out cacheResult)) { // If we get here we've got a published conversion or error, so we can stop searching. return(!cacheResult.IsError); } if (targetType == sourceType || targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo())) { // If we find a trivial conversion, then that will work. return(true); } if (!targetType.GetTypeInfo().IsInterface) { var message = Resources.FormatConverter_TypeMustBeInterface(targetType.FullName, sourceType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } // This is a combination we haven't seen before, and it *might* support proxy generation, so let's // start trying. var verificationResult = new VerificationResult(); context.Visited.Add(key, verificationResult); // We support conversions from IList<T> -> IReadOnlyList<U> and IReadOnlyList<T> -> IReadOnlyList<U> if (targetType.GetTypeInfo().IsGenericType&& targetType.GetTypeInfo().GetGenericTypeDefinition() == typeof(IReadOnlyList <>)) { var sourceInterfaceType = GetGenericImplementation(sourceType, typeof(IList <>)); if (sourceInterfaceType != null) { var targetElementType = targetType.GetTypeInfo().GenericTypeArguments[0]; var sourceElementType = sourceInterfaceType.GetTypeInfo().GenericTypeArguments[0]; var elementKey = new Tuple <Type, Type>(sourceElementType, targetElementType); if (!VerifyProxySupport(context, elementKey)) { var error = context.Cache[elementKey]; Debug.Assert(error != null && error.IsError); context.Cache[key] = error; return(false); } VerificationResult elementResult; context.Visited.TryGetValue(elementKey, out elementResult); var proxyType = elementResult?.Type?.GetTypeInfo() ?? (TypeInfo)elementResult?.TypeBuilder; if (proxyType == null) { // No proxy needed for elements. verificationResult.Type = typeof(ProxyList <,>).MakeGenericType(elementKey.Item1, elementKey.Item2); verificationResult.Constructor = verificationResult.Type.GetTypeInfo().DeclaredConstructors.First(); } else { // We need to proxy each of the elements. Let's generate a type. GenerateProxyTypeForList(elementKey.Item1, elementKey.Item2, proxyType.AsType(), verificationResult); } return(true); } } // This doesn't match any of our interface conversions, so we'll codegen a proxy. var propertyMappings = new List <KeyValuePair <PropertyInfo, PropertyInfo> >(); var sourceProperties = sourceType.GetRuntimeProperties(); var targetProperties = targetType .GetTypeInfo() .ImplementedInterfaces .SelectMany(i => i.GetRuntimeProperties()) .Concat(targetType.GetRuntimeProperties()); foreach (var targetProperty in targetProperties) { if (!targetProperty.CanRead) { var message = Resources.FormatConverter_PropertyMustHaveGetter( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } if (targetProperty.CanWrite) { var message = Resources.FormatConverter_PropertyMustNotHaveSetter( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } if (targetProperty.GetIndexParameters()?.Length > 0) { var message = Resources.FormatConverter_PropertyMustNotHaveIndexParameters( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } // To allow for flexible versioning, we want to allow missing properties in the source. // // For now we'll just store null, and later generate a stub getter that returns default(T). var sourceProperty = sourceProperties.Where(p => p.Name == targetProperty.Name).FirstOrDefault(); if (sourceProperty != null && sourceProperty.CanRead && sourceProperty.GetMethod?.IsPublic == true) { var propertyKey = new Tuple <Type, Type>(sourceProperty.PropertyType, targetProperty.PropertyType); if (!VerifyProxySupport(context, propertyKey)) { // There's an error here, so bubble it up and cache it. var error = context.Cache[propertyKey]; Debug.Assert(error != null && error.IsError); context.Cache[key] = ProxyTypeCacheResult.FromError(key, error.Error); return(false); } propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, sourceProperty)); } else { propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, null)); } } verificationResult.Mappings = propertyMappings; GenerateProxyTypeFromProperties(sourceType, targetType, verificationResult); return(true); }
public static Type GetProxyType(ProxyTypeCache cache, Type targetType, Type sourceType) { if (targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo())) { return(null); } var key = new Tuple <Type, Type>(sourceType, targetType); ProxyTypeCacheResult result; if (!cache.TryGetValue(key, out result)) { var context = new ProxyBuilderContext(cache, targetType, sourceType); // Check that all required types are proxy-able - this will create the TypeBuilder, Constructor, // and property mappings. // // We need to create the TypeBuilder and Constructor up front to deal with cycles that can occur // when generating the proxy properties. if (!VerifyProxySupport(context, context.Key)) { var error = cache[key]; Debug.Assert(error != null && error.IsError); throw new InvalidOperationException(error.Error); } Debug.Assert(context.Visited.ContainsKey(context.Key)); // Now that we've generated all of the constructors for the proxies, we can generate the properties. foreach (var verificationResult in context.Visited) { if (verificationResult.Value.Mappings != null) { AddProperties( context, verificationResult.Value.TypeBuilder, verificationResult.Value.Mappings); } } // Now generate the type foreach (var verificationResult in context.Visited) { if (verificationResult.Value.TypeBuilder != null) { verificationResult.Value.Type = verificationResult.Value.TypeBuilder.CreateTypeInfo().AsType(); } } // We only want to publish the results after all of the proxies are totally generated. foreach (var verificationResult in context.Visited) { cache[verificationResult.Key] = ProxyTypeCacheResult.FromType( verificationResult.Key, verificationResult.Value.Type, verificationResult.Value.Constructor); } return(context.Visited[context.Key].Type); } else if (result.IsError) { throw new InvalidOperationException(result.Error); } else if (result.Type == null) { // This is an identity convertion return(null); } else { return(result.Type); } }