/// <summary> /// Constructor used for the top-level search /// </summary> /// <param name="type"></param> /// <param name="owner"></param> public TargetTypeSelectorParams(Type type, TargetTypeSelector owner) : this(type) { RootTargets = owner?.RootTargets; // variance always starts off enabled. EnableVariance = true; bool enableContravariance = true; this._forceContravariance = GetInternalContravarianceOverride(Type); if (this._forceContravariance == null) { if (RootTargets != null) { enableContravariance = RootTargets.GetOption( Type, Options.EnableContravariance.Default); } } else { enableContravariance = this._forceContravariance.Value; } if (enableContravariance) { Contravariance = Contravariance.BasesAndInterfaces; } else { Contravariance = Contravariance.None; } }
private IEnumerable <Type> Run(TargetTypeSelectorParams search) { // for IFoo<IEnumerable<IBar<in T>>>, this should return something like // IFoo<IEnumerable<IBar<T>>>, // IFoo<IEnumerable<IBar<T[Base0..n]>>>, //foreach base and interface of T if contravariant // IFoo<IEnumerable<IBar<>>, // IFoo<IEnumerable<> // IFoo<> List <Type> toReturn = new List <Type>(20) { search.Type }; if (!VariantMatches.TryGetValue(search.Type, out HashSet <Type> variantMatches)) { variantMatches = VariantMatches[search.Type] = new HashSet <Type>(); } // these get added after all other bases and interfaces have been added. List <Type> explicitlyAddedBases = new List <Type>(); Type[] temp; if (search.Type.IsGenericType && !search.Type.IsGenericTypeDefinition) { // now return any covariant matches if applicable // NOTE - if the search is for a type parameter, then we don't perform this operation // as we're only interested in materialising covariant types which we know *should* materialise // at least one target match. if (search.TypeParameter == null && !search.Type.IsValueType && RootTargets != null) { temp = RootTargets.GetKnownCovariantTypes(search.Type).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } // for every generic type, there is at least two versions - the closed and the open // when you consider, also, that a generic parameter might also be a generic, with multiple // versions - you can see that things can get icky. var argSearches = search.Type.GetGenericArguments() .Zip(search.Type.GetGenericTypeDefinition().GetGenericArguments(), (arg, param) => new TargetTypeSelectorParams( arg, param, search) ).ToArray(); var typeParamSearchLists = argSearches.Select(t => Run(t).ToArray()).ToArray(); var genericType = search.Type.GetGenericTypeDefinition(); Type[] combinationArray = null; Type compatibleType = null; // Note: the first result will be equal to search.Type, hence Skip(1) foreach (var combination in typeParamSearchLists.Permutate().Skip(1)) { combinationArray = combination.ToArray(); try { compatibleType = genericType.MakeGenericType(combinationArray); } catch (Exception) { // ignore things like constraints etc because it could easily happen and // it's actually very hard to check up front. continue; } // check if this combination counts as a variant match if (!compatibleType.ContainsGenericParameters) { for (var f = 0; f < combinationArray.Length; f++) { if (VariantMatches.TryGetValue(argSearches[f].Type, out var typeArgVariants) && typeArgVariants.Contains(combinationArray[f])) { variantMatches.Add(compatibleType); break; } } } toReturn.Add(compatibleType); if (search.TypeParameter == null && !search.Type.IsValueType && RootTargets != null) { temp = RootTargets.GetKnownCovariantTypes(compatibleType).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } } toReturn.Add(genericType); } bool isArray = search.Type.IsArray; Type arrayElemType = null; bool isValueTypeArray = false; bool addElemTypeInterfaces = false; bool addArrayBases = false; List <Type> allArrayTypes = new List <Type>(); if (isArray) { allArrayTypes.Add(search.Type); arrayElemType = search.Type.GetElementType(); isValueTypeArray = arrayElemType.IsValueType; if (!isValueTypeArray) { addElemTypeInterfaces = arrayElemType.IsInterface; addArrayBases = !addElemTypeInterfaces; } } if (search.EnableVariance) { if (search.TypeParameterIsContravariant && (search.Contravariance & Contravariance.BasesAndInterfaces) != Contravariance.None) { // note - when spawning child searches, this search's parent is passed as the parent // to the newly created search. This ensures that the contravariant search direction // is calculated correctly if ((search.Contravariance & Contravariance.Bases) == Contravariance.Bases) { if (isArray) { explicitlyAddedBases.Add(typeof(Array)); explicitlyAddedBases.Add(typeof(object)); // Arrays are weird. Given an instance i of type A[], where A is a reference or interface type, // then i can be implicitly converted to an array of any base or interface ('supertype') of A; and // can also be implicitly converted to any interface or base of an array of that supertype. // So, given Foo : IFoo // We can have IFoo[] a = new Foo[10]; // We can also have IList<IFoo> a = new Foo[10]; // Because IList<T> is an interface of IFoo[]. // The same also holds true for any contravariant type argument, so: // Action<IList<IFoo>> a = new Action<Foo[]>(foos => <<blah>>) is allowed. if (addArrayBases) { allArrayTypes.AddRange(search.Type.GetBaseArrayTypes()); } else if (addElemTypeInterfaces) { allArrayTypes.AddRange(search.Type.GetInterfaceArrayTypes()); } // loop through the bases, constructing array types for each and adding them // note we skip the first because it'll be the search type. temp = allArrayTypes.Skip(1) .SelectMany(tt => Run(new TargetTypeSelectorParams(tt, search.TypeParameter, search.Parent, Contravariance.Bases))) .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } // if it's a class then iterate the bases if (!search.Type.IsInterface && !search.Type.IsValueType && search.Type != typeof(object)) { if (!explicitlyAddedBases.Contains(typeof(object))) { explicitlyAddedBases.Add(typeof(object)); } // note - disable interfaces when recursing into the base // note also - ignore 'object' temp = Run(new TargetTypeSelectorParams(search.Type.BaseType, search.TypeParameter, search.Parent, Contravariance.Bases)) .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } } if ((search.Contravariance & Contravariance.Interfaces) == Contravariance.Interfaces) { // don't check interfaces when type is a value type if (!search.Type.IsValueType) { if (isArray) { // have to include all the interfaces of all the array types that are compatible per array covariance temp = allArrayTypes .SelectMany(t => t.GetInterfaces() .SelectMany(tt => Run(new TargetTypeSelectorParams(tt, search.TypeParameter, search.Parent, Contravariance.Bases)))) .OrderBy(t => t, DescendingTypeOrder.Instance) .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } else { temp = search.Type.GetInterfaces() .SelectMany(t => Run(new TargetTypeSelectorParams(t, search.TypeParameter, search.Parent, Contravariance.Bases))) .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray(); variantMatches.UnionWith(temp); toReturn.AddRange(temp); } } } variantMatches.UnionWith(explicitlyAddedBases); toReturn.AddRange(explicitlyAddedBases); } } return(toReturn); }
public TargetTypeSelectorParams(Type type, Type typeParameter = null, TargetTypeSelectorParams parent = null, Contravariance?contravariantSearchType = null) : this(type) { Parent = parent; RootTargets = parent?.RootTargets; TypeParameter = typeParameter; // We enable variance if we have no parent // Or if we have a variant type parameter and // the parent hasn't disabled variance. EnableVariance = parent == null || (TypeParameterIsVariant && Parent.EnableVariance); if (EnableVariance) { bool enableContravariance; if (ForceContravariance == null) { // start off always enabled enableContravariance = true; var overridenContravariance = GetInternalContravarianceOverride(Type); if (overridenContravariance != null) { // once it's forced, all child searches will avoid testing the EnableContravariance option this._forceContravariance = overridenContravariance; enableContravariance = overridenContravariance.Value; } else if (RootTargets != null) { enableContravariance = RootTargets.GetOption( Type, Options.EnableContravariance.Default); } } else { enableContravariance = ForceContravariance.Value; } if (contravariantSearchType != null) { Contravariance = contravariantSearchType.Value; } else { if (!enableContravariance) { Contravariance = Contravariance.None; } else { // if the parent has its contravariance search set to None, we inherit that // and move on. if (Parent?.Contravariance == Contravariance.None) { Contravariance = Contravariance.None; } else { var numContras = TypeParameterChain.Count(t => t.IsContravariantTypeParameter()); if (numContras <= 1 || (numContras % 2) == 1) { Contravariance = Contravariance.BasesAndInterfaces; } else { Contravariance = Contravariance.Derived; } } } } } }