Ich möchte in der Lage sein, einen generischen Ausdruck zu schreiben, den ein Benutzer verwenden kann, um zu beschreiben, wie er eine Konvertierung über eine Familie von Typen vornehmen möchte.Wie kann ich einen Typparameter in einem Ausdrucksbaum ersetzen?
Der Ausdruck könnte etwas wie folgt aussehen:
Expression<Func<PlaceHolder,object>> sample =
x=> (object)EqualityComparer<PlaceHolder>.GetHashCode(x)
ich es in umwandeln möchte ::
Expression<Func<Foo,object>> sample =
x=> (object)EqualityComparer<Foo>.GetHashCode(x)
Ich kann nur den Ausdruck besuchen, und PlaceHolder
Parameter mit x ersetzen, aber dann Ich kann den generischen Typenaufruf nicht auflösen.
Der Ausdruck wird vom Benutzer bereitgestellt, und Sie können einem Ausdruck keine generische Methode zuweisen.
Das Endergebnis ist immer ein Objekt zurückgeben und der Ausdruck wird immer von T=>object
sein. Ich werde einen neuen Ausdruck für jedes Objekt kompilieren, das die Standardregel ersetzen wird.
Hier ist mein bestehender Code, der funktioniert, aber es scheint sehr kompliziert.
// ReSharper disable once InconsistentNaming
// By design this is supposed to look like a generic parameter.
public enum TEnum : long
{
}
internal sealed class EnumReplacer : ExpressionVisitor
{
private Type ReplacePlaceHolder(Type type)
{
if (type.IsByRef)
{
return ReplacePlaceHolder(type.GetElementType()).MakeByRefType();
}
if (type.IsArray)
{
// expressionTrees can only deal with 1d arrays.
return ReplacePlaceHolder(type.GetElementType()).MakeArrayType();
}
if (type.IsGenericType)
{
var typeDef = type.GetGenericTypeDefinition();
var args = Array.ConvertAll(type.GetGenericArguments(), t => ReplacePlaceHolder(t));
return typeDef.MakeGenericType(args);
}
if (type == typeof(TEnum))
{
return _enumParam.Type;
}
return type;
}
private MethodBase ReplacePlaceHolder(MethodBase method)
{
var newCandidate = method;
var currentParams = method.IsGenericMethod ? ((MethodInfo)method).GetGenericMethodDefinition().GetParameters() : method.GetParameters();
// ReSharper disable once PossibleNullReferenceException
if (method.DeclaringType.IsGenericType)
{
var newType = ReplacePlaceHolder(method.DeclaringType);
var methodCandidates = newType.GetMembers()
.OfType<MethodBase>()
.Where(x => x.Name == method.Name
&& x.IsStatic == method.IsStatic
&& x.IsGenericMethod == method.IsGenericMethod).ToArray();
// grab the first method that wins. Not 100% correct, but close enough.
// yes an evil person could define a class like this::
// class C<T>{
// public object Foo<T>(T b){return null;}
// public object Foo(PlaceHolderEnum b){return new object();}
// }
// my code would prefer the former, where as C#6 likes the later.
newCandidate = methodCandidates.First(m => TestParameters(m, currentParams));
}
if (method.IsGenericMethod)
{
var genericArgs = method.GetGenericArguments();
genericArgs = Array.ConvertAll(genericArgs, temp => ReplacePlaceHolder(temp));
newCandidate = ((MethodInfo)newCandidate).GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
}
return newCandidate;
}
private Expression ReplacePlaceHolder(MethodBase method, Expression target, ReadOnlyCollection<Expression> arguments)
{
// no point in not doing this.
var newArgs = Visit(arguments);
if (target != null)
{
target = Visit(target);
}
var newCandidate = ReplacePlaceHolder(method);
MethodInfo info = newCandidate as MethodInfo;
if (info != null)
{
return Expression.Call(target, info, newArgs);
}
return Expression.New((ConstructorInfo)newCandidate, newArgs);
}
private bool TestParameters(MethodBase candidate, ParameterInfo[] currentParams)
{
var candidateParams = candidate.GetParameters();
if (candidateParams.Length != currentParams.Length) return false;
for (int i = 0; i < currentParams.Length; i++)
{
// the names should match.
if (currentParams[i].Name != candidateParams[i].Name) return false;
var curType = currentParams[i].ParameterType;
var candidateType = candidateParams[i].ParameterType;
// Either they are the same generic type arg, or they are the same type after replacements.
if (!((curType.IsGenericParameter &&
curType.GenericParameterPosition == candidateType.GenericParameterPosition)
|| ReplacePlaceHolder(curType) == candidateType))
{
return false;
}
}
return true;
}
private readonly ParameterExpression _enumParam;
public EnumReplacer(ParameterExpression enumParam)
{
_enumParam = enumParam;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(TEnum))
{
return _enumParam;
}
if (node.Type == typeof(TypeCode))
{
return Expression.Constant(Type.GetTypeCode(_enumParam.Type));
}
return base.VisitParameter(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked)
{
var t = ReplacePlaceHolder(node.Type);
// this isn't perfect. The compiler loves inserting random casts. To be protective and offer the most range, TEnum should be a long.
var method = node.Method == null ? null : ReplacePlaceHolder(node.Method);
return node.NodeType == ExpressionType.ConvertChecked
? Expression.ConvertChecked(Visit(node.Operand), t, (MethodInfo) method)
: Expression.Convert(Visit(node.Operand), t, (MethodInfo) method);
}
if (node.Operand.Type == typeof(TEnum))
{
var operand = Visit(node.Operand);
return node.Update(operand);
}
return base.VisitUnary(node);
}
private MemberInfo ReplacePlaceHolder(MemberInfo member)
{
if (member.MemberType == MemberTypes.Method || member.MemberType == MemberTypes.Constructor)
{
return ReplacePlaceHolder((MethodBase) member);
}
var newType = ReplacePlaceHolder(member.DeclaringType);
var newMember = newType.GetMembers().First(x => x.Name == member.Name);
return newMember;
}
protected override Expression VisitNewArray(NewArrayExpression node)
{
var children = Visit(node.Expressions);
// Despite returning T[], it expects T.
var type = ReplacePlaceHolder(node.Type.GetElementType());
return Expression.NewArrayInit(type, children);
}
protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
{
var newMember = ReplacePlaceHolder(node.Member);
var bindings = node.Bindings.Select(x => VisitMemberBinding(x));
return Expression.MemberBind(newMember, bindings);
}
protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
{
var prop = ReplacePlaceHolder(node.Member);
var inits = node.Initializers.Select(x => VisitElementInit(x));
return Expression.ListBind(prop, inits);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
return ReplacePlaceHolder(node.Method, node.Object, node.Arguments);
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
var expr = Visit(node.Expression);
var prop = ReplacePlaceHolder(node.Member);
return Expression.Bind(prop, expr);
}
protected override ElementInit VisitElementInit(ElementInit node)
{
var method = ReplacePlaceHolder(node.AddMethod);
var args = Visit(node.Arguments);
return Expression.ElementInit((MethodInfo)method, args);
}
protected override Expression VisitNew(NewExpression node)
{
return ReplacePlaceHolder(node.Constructor, null, node.Arguments);
}
protected override Expression VisitConstant(ConstantExpression node)
{
// replace typeof expression
if (node.Type == typeof(Type) && (Type)node.Value == typeof(TEnum))
{
return Expression.Constant(_enumParam.Type);
}
// explicit usage of default(TEnum) or (TEnum)456
if (node.Type == typeof(TEnum))
{
return Expression.Constant(Enum.ToObject(_enumParam.Type, node.Value));
}
return base.VisitConstant(node);
}
}
Verwendung ist wie so ::
class Program
{
public class Holder
{
public int Foo { get; set; }
}
public class Foo<T1,T2> : IEnumerable
{
public object GenericMethod<TM, TM2>(TM2 blarg) => blarg.ToString();
public IList<Foo<T1, T2>> T { get; set; } = new List<Foo<T1, T2>>();
public T1 Prop { get; set; }
public void Add(int i) { }
public Holder Holder { get; set; } = new Holder {};
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
public enum LongEnum:ulong
{
}
static void Main(string[] args)
{
Expression<Func<TEnum, TypeCode, object>> evilTest = (x,t) =>
TypeCode.UInt64 == t
? (object)new Dictionary<TEnum, TypeCode>().TryGetValue(checked((x - 407)), out t)
: new Foo<string, TEnum> { Holder = {Foo =6}, T = new []
{
new Foo<string, TEnum>
{
T = {
new Foo<string, TEnum>{1,2,3,4,5,6,7,8,9,10,11,12}
}
},
new Foo<string, TEnum>
{
Prop = $"What up hello? {args}"
}
}}.GenericMethod<string, TEnum>(x);
Console.WriteLine(evilTest);
var p = Expression.Parameter(typeof(LongEnum), "long");
var expressionBody = new EnumReplacer(p).Visit(evilTest.Body);
var q = Expression.Lambda<Func<LongEnum, object>>(expressionBody, p);
var func =q.Compile();
var res = func.Invoke((LongEnum)1234567890123Ul);
Dies wird unmöglich sein. Es wird keine Möglichkeit geben zu unterscheiden, welche Verwendungen dieses Typs geändert werden müssen und welche nicht. – Servy
Der Typparameter einer generischen Klasse/Methode muss während der Kompilierzeit bekannt sein. Ich glaube also nicht, dass dies funktionieren wird, wenn Sie nicht zur Laufzeit einen neuen Typ erstellen. –
Der Code wird vor der Verwendung kompiliert. –