Dưới đây là thực hiện của tôi về 3 phương pháp khuyến nông:
bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)
Đầu tiên cho phép bạn kiểm tra xem loại được xây dựng kín có khớp với định nghĩa kiểu được xây dựng không. Nếu vậy, thứ hai có thể phỏng đoán tất cả các đối số kiểu được yêu cầu để trả về một cấu trúc khép kín được xây dựng từ một kiểu được xây dựng khép kín nhất định. Cuối cùng, phương thức thứ ba có thể tự động giải quyết tất cả điều này cho các phương thức. Lưu ý rằng các phương pháp này sẽ không thất bại hoặc trả về false nếu bạn chuyển một loại được xây dựng mở khác làm đối số kiểu "được xây dựng khép kín", miễn là loại thứ hai này tuân thủ tất cả các ràng buộc kiểu của kiểu mở ban đầu được xây dựng. . Thay vào đó, họ sẽ giải quyết càng nhiều thông tin loại càng tốt từ các loại đã cho. Do đó, nếu bạn muốn đảm bảo độ phân giải đã đưa ra một loại được đóng hoàn toàn, bạn nên kiểm tra xem kết quả của ContainsGenericParameters
có trả về sai không. Điều này phù hợp với hành vi của .NET MakeGenericType
hoặc MakeGenericMethod
.
Cũng lưu ý rằng tôi không được thông báo đầy đủ về đồng ý và đối nghịch, vì vậy những triển khai này có thể không chính xác trong vấn đề đó.
sử dụng Ví dụ:
public static void GenericMethod<T0, T1>(T0 direct, IEnumerable<T1> generic)
where T0 : struct
where T1 : class, new(), IInterface
{ }
public interface IInterface { }
public class CandidateA : IInterface { private CandidateA(); }
public struct CandidateB : IInterface { }
public class CandidateC { public CandidateC(); }
public class CandidateD : IInterface { public CandidateD(); }
var method = GetMethod("GenericMethod");
var type0 = method.GetParameters()[0].ParameterType;
var type1 = method.GetParameters()[1].ParameterType;
// Results:
type0.CanMakeGenericTypeVia(typeof(int)) // true
type0.CanMakeGenericTypeVia(typeof(IList)) // false, fails struct
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateA>))
// false, fails new()
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateB>))
// false, fails class
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateC>))
// false, fails : IInterface
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateD>))
// true
type0.MakeGenericTypeVia(typeof(int))
// typeof(int)
type1.MakeGenericTypeVia(typeof(List<CandidateD>))
// IEnumerable<CandidateD>
method.MakeGenericMethodVia(123.GetType(), (new CandidateD[0]).GetType())
// GenericMethod(int, IEnumerable<CandidateD>)
method.MakeGenericMethodVia(123.GetType(), type1)
// GenericMethod<T1>(int, IEnumerable<T1>)
// (partial resolution)
Thực hiện:
public static bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
{
if (openConstructedType == null)
{
throw new ArgumentNullException("openConstructedType");
}
if (closedConstructedType == null)
{
throw new ArgumentNullException("closedConstructedType");
}
if (openConstructedType.IsGenericParameter) // e.g.: T
{
// The open-constructed type is a generic parameter.
// First, check if all special attribute constraints are respected.
var constraintAttributes = openConstructedType.GenericParameterAttributes;
if (constraintAttributes != GenericParameterAttributes.None)
{
// e.g.: where T : struct
if (constraintAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) &&
!closedConstructedType.IsValueType)
{
return false;
}
// e.g.: where T : class
if (constraintAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) &&
closedConstructedType.IsValueType)
{
return false;
}
// e.g.: where T : new()
if (constraintAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) &&
closedConstructedType.GetConstructor(Type.EmptyTypes) == null)
{
return false;
}
// TODO: Covariance and contravariance?
}
// Then, check if all type constraints are respected.
// e.g.: where T : BaseType, IInterface1, IInterface2
foreach (var constraint in openConstructedType.GetGenericParameterConstraints())
{
if (!constraint.IsAssignableFrom(closedConstructedType))
{
return false;
}
}
return true;
}
else if (openConstructedType.ContainsGenericParameters)
{
// The open-constructed type is not a generic parameter but contains generic parameters.
// It could be either a generic type or an array.
if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2>
{
// The open-constructed type is a generic type.
var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,>
var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 }
// Check a list of possible candidate closed-constructed types:
// - the closed-constructed type itself
// - its base type, if any (i.e.: if the closed-constructed type is not object)
// - its implemented interfaces
var inheritedClosedConstructedTypes = new List<Type>();
inheritedClosedConstructedTypes.Add(closedConstructedType);
if (closedConstructedType.BaseType != null)
{
inheritedClosedConstructedTypes.Add(closedConstructedType.BaseType);
}
inheritedClosedConstructedTypes.AddRange(closedConstructedType.GetInterfaces());
foreach (var inheritedClosedConstructedType in inheritedClosedConstructedTypes)
{
if (inheritedClosedConstructedType.IsGenericType &&
inheritedClosedConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition)
{
// The inherited closed-constructed type and the open-constructed type share the same generic definition.
var inheritedClosedConstructedGenericArguments = inheritedClosedConstructedType.GetGenericArguments(); // e.g.: { float, int, string }
// For each open-constructed generic argument, recursively check if it
// can be made into a closed-constructed type via the closed-constructed generic argument.
for (int i = 0; i < openConstructedGenericArguments.Length; i++)
{
if (!openConstructedGenericArguments[i].CanMakeGenericTypeVia(inheritedClosedConstructedGenericArguments[i])) // !T1.IsAssignableFromGeneric(float)
{
return false;
}
}
// The inherited closed-constructed type matches the generic definition of
// the open-constructed type and each of its type arguments are assignable to each equivalent type
// argument of the constraint.
return true;
}
}
// The open-constructed type contains generic parameters, but no
// inherited closed-constructed type has a matching generic definition.
return false;
}
else if (openConstructedType.IsArray) // e.g. T[]
{
// The open-constructed type is an array.
if (!closedConstructedType.IsArray ||
closedConstructedType.GetArrayRank() != openConstructedType.GetArrayRank())
{
// Fail if the closed-constructed type isn't an array of the same rank.
return false;
}
var openConstructedElementType = openConstructedType.GetElementType();
var closedConstructedElementType = closedConstructedType.GetElementType();
return openConstructedElementType.CanMakeGenericTypeVia(closedConstructedElementType);
}
else
{
// I don't believe this can ever happen.
throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type.");
}
}
else
{
// The open-constructed type does not contain generic parameters,
// we can proceed to a regular closed-type check.
return openConstructedType.IsAssignableFrom(closedConstructedType);
}
}
public static Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType, Dictionary<Type, Type> resolvedGenericParameters, bool safe = true)
{
if (openConstructedType == null)
{
throw new ArgumentNullException("openConstructedType");
}
if (closedConstructedType == null)
{
throw new ArgumentNullException("closedConstructedType");
}
if (resolvedGenericParameters == null)
{
throw new ArgumentNullException("resolvedGenericParameters");
}
if (safe && !openConstructedType.CanMakeGenericTypeVia(closedConstructedType))
{
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
if (openConstructedType.IsGenericParameter) // e.g.: T
{
// The open-constructed type is a generic parameter.
// We can directly map it to the closed-constructed type.
// Because this is the lowest possible level of type resolution,
// we will add this entry to our list of resolved generic parameters
// in case we need it later (e.g. for resolving generic methods).
// Note that we allow an open-constructed type to "make" another
// open-constructed type, as long as the former respects all of
// the latter's constraints. Therefore, we will only add the resolved
// parameter to our dictionary if it actually is resolved.
if (!closedConstructedType.ContainsGenericParameters)
{
if (resolvedGenericParameters.ContainsKey(openConstructedType))
{
if (resolvedGenericParameters[openConstructedType] != closedConstructedType)
{
throw new InvalidOperationException("Nested generic parameters resolve to different values.");
}
}
else
{
resolvedGenericParameters.Add(openConstructedType, closedConstructedType);
}
}
return closedConstructedType;
}
else if (openConstructedType.ContainsGenericParameters) // e.g.: Generic<T1, int, T2>
{
// The open-constructed type is not a generic parameter but contains generic parameters.
// It could be either a generic type or an array.
if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2>
{
// The open-constructed type is a generic type.
var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,>
var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 }
// Check a list of possible candidate closed-constructed types:
// - the closed-constructed type itself
// - its base type, if any (i.e.: if the closed-constructed type is not object)
// - its implemented interfaces
var inheritedCloseConstructedTypes = new List<Type>();
inheritedCloseConstructedTypes.Add(closedConstructedType);
if (closedConstructedType.BaseType != null)
{
inheritedCloseConstructedTypes.Add(closedConstructedType.BaseType);
}
inheritedCloseConstructedTypes.AddRange(closedConstructedType.GetInterfaces());
foreach (var inheritedCloseConstructedType in inheritedCloseConstructedTypes)
{
if (inheritedCloseConstructedType.IsGenericType &&
inheritedCloseConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition)
{
// The inherited closed-constructed type and the open-constructed type share the same generic definition.
var inheritedClosedConstructedGenericArguments = inheritedCloseConstructedType.GetGenericArguments(); // e.g.: { float, int, string }
// For each inherited open-constructed type generic argument, recursively resolve it
// via the equivalent closed-constructed type generic argument.
var closedConstructedGenericArguments = new Type[openConstructedGenericArguments.Length];
for (int j = 0; j < openConstructedGenericArguments.Length; j++)
{
closedConstructedGenericArguments[j] = MakeGenericTypeVia
(
openConstructedGenericArguments[j],
inheritedClosedConstructedGenericArguments[j],
resolvedGenericParameters,
safe: false // We recursively checked before, no need to do it again
);
// e.g.: Resolve(T1, float)
}
// Construct the final closed-constructed type from the resolved arguments
return openConstructedGenericDefinition.MakeGenericType(closedConstructedGenericArguments);
}
}
// The open-constructed type contains generic parameters, but no
// inherited closed-constructed type has a matching generic definition.
// This cannot happen in safe mode, but could in unsafe mode.
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
else if (openConstructedType.IsArray) // e.g. T[]
{
var arrayRank = openConstructedType.GetArrayRank();
// The open-constructed type is an array.
if (!closedConstructedType.IsArray ||
closedConstructedType.GetArrayRank() != arrayRank)
{
// Fail if the closed-constructed type isn't an array of the same rank.
// This cannot happen in safe mode, but could in unsafe mode.
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
var openConstructedElementType = openConstructedType.GetElementType();
var closedConstructedElementType = closedConstructedType.GetElementType();
return openConstructedElementType.MakeGenericTypeVia
(
closedConstructedElementType,
resolvedGenericParameters,
safe: false
).MakeArrayType(arrayRank);
}
else
{
// I don't believe this can ever happen.
throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type.");
}
}
else
{
// The open-constructed type does not contain generic parameters,
// it is by definition already resolved.
return openConstructedType;
}
}
public static MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)
{
if (openConstructedMethod == null)
{
throw new ArgumentNullException("openConstructedMethod");
}
if (closedConstructedParameterTypes == null)
{
throw new ArgumentNullException("closedConstructedParameterTypes");
}
if (!openConstructedMethod.ContainsGenericParameters)
{
// The method contains no generic parameters,
// it is by definition already resolved.
return openConstructedMethod;
}
var openConstructedParameterTypes = openConstructedMethod.GetParameters().Select(p => p.ParameterType).ToArray();
if (openConstructedParameterTypes.Length != closedConstructedParameterTypes.Length)
{
throw new ArgumentOutOfRangeException("closedConstructedParameterTypes");
}
var resolvedGenericParameters = new Dictionary<Type, Type>();
for (int i = 0; i < openConstructedParameterTypes.Length; i++)
{
// Resolve each open-constructed parameter type via the equivalent
// closed-constructed parameter type.
var openConstructedParameterType = openConstructedParameterTypes[i];
var closedConstructedParameterType = closedConstructedParameterTypes[i];
openConstructedParameterType.MakeGenericTypeVia(closedConstructedParameterType, resolvedGenericParameters);
}
// Construct the final closed-constructed method from the resolved arguments
var openConstructedGenericArguments = openConstructedMethod.GetGenericArguments();
var closedConstructedGenericArguments = openConstructedGenericArguments.Select(openConstructedGenericArgument =>
{
// If the generic argument has been successfully resolved, use it;
// otherwise, leave the open-constructe argument in place.
if (resolvedGenericParameters.ContainsKey(openConstructedGenericArgument))
{
return resolvedGenericParameters[openConstructedGenericArgument];
}
else
{
return openConstructedGenericArgument;
}
}).ToArray();
return openConstructedMethod.MakeGenericMethod(closedConstructedGenericArguments);
}
Hội chợ đủ, nhờ Jon. Đã hy vọng để tránh điều đó nhưng như hiệu suất không phải là một vấn đề trong mã này, nếu bạn nói đó là cách dễ nhất là nó đủ tốt cho tôi! – Simon
Đây không phải là cách tiếp cận "người lười biếng"? Tôi đã bị đập vào hộp sọ bởi nhiều người cao tuổi trong những năm đầu của tôi vì đã sử dụng ngoại lệ để kiểm tra mọi thứ thay vì kiểm tra mọi thứ để tránh một ngoại lệ. –
Nếu bạn có thể cải thiện câu trả lời của Jon, tôi sẽ vui hơn Joel :-) – Simon