简体   繁体   English

如何通过反射设置作为通用接口的字段的值?

[英]How do I set value of field that is a generic interface through reflection?

I want to be able to use reflection to get a member of a class that is, say, of type IList and I want to create a delegate for it so that I pay less for reflection. 我希望能够使用反射来获得IList类型的类的成员,并且我想为其创建一个委托,以便为反射花更少的钱。 All of this so that late on, I am able to call the delegate by passing in my instance and a new List. 所有这些使我可以通过传入实例和新的List来调用委托。 C# is giving me invalid cast exceptions whenever I try this. 每当我尝试这样做时,C#都会给我无效的强制转换异常。 Am I missing something silly with generics? 我在泛型中缺少一些愚蠢的东西吗?

I have tried using interface fields with no generics and those seem to work just fine. 我尝试过使用没有泛型的接口字段,并且这些字段似乎工作正常。 I only seem to encounter the issue when using generics. 我似乎只在使用泛型时遇到问题。 I also tried to use Convert.ChangeType to convert my input to the desired type and I get errors about List<> not implementing IConvertible. 我还尝试使用Convert.ChangeType将我的输入转换为所需的类型,并且出现有关List <>的错误,该错误未实现IConvertible。 If I can set a value of type IList<> from a concrete List<> using reflection, I will be happy :) 如果我可以使用反射从具体的List <>设置IList <>类型的值,我会很高兴的:)

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public class MyObject
{
    public IList<int> MyField;
}

public class ReflectionPerfTesting
{

    public void Run()
    {
        var testObject = new MyObject();
        var field = testObject.GetType().GetField("MyField", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        InvokeUsingReflection(field, testObject);
    }

    private void InvokeUsingReflection(FieldInfo fieldInfo, object testObject)
    {
        var fieldType = fieldInfo.FieldType;
        var sourceType = fieldInfo.DeclaringType;

        // ZAS: create value parameter
        var valueParam = Expression.Parameter(fieldType);

        // ZAS: create targetParameter
        var sourceParam = Expression.Parameter(sourceType);

        var field = Expression.Field(sourceParam, fieldInfo);
        var returnExpression = (Expression)Expression.Assign(field, valueParam);
        if (!fieldType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, fieldType);
        }

        // ZAS: create generic delegate from definition
        var delegateType = typeof(Action<,>);
        var genericType = delegateType.MakeGenericType(sourceType, fieldType);

        // ZAS: Create lambda for faster access to setter using reflection. This is meant to be cached!
        var lambdaExpression = Expression.Lambda(genericType, returnExpression, sourceParam, valueParam);
        var lambda = lambdaExpression.Compile();
        var lambdaInvokeMethod = lambda.GetType().GetMethod("Invoke");

        var list = new List<int>();
        lambdaInvokeMethod.Invoke(testObject, new object[] { list });
    }

}

Expected results are for me to be able to cache a "setter" to the property and support setting the value of any type including generics and interfaces. 对我来说,预期的结果是能够将“ setter”缓存到该属性,并支持设置任何类型的值,包括泛型和接口。 My current result is that setting values of any type work EXCEPT when there are generics... so I must be doing something incorrectly. 我当前的结果是,在有泛型的情况下,任何类型的设置值都可以工作,所以我必须做错了什么。

Your lambda call is wrong. 您的lambda通话有误。 You have to call the Invoke method on the lambda object passing the target aswell as the arguments. 您必须在传递目标以及参数的lambda对象上Invoke方法。

If you fix the call you get the desired result: 如果解决此问题,则可获得所需结果:

testObject.Dump();

var list = new List<int>();
lambdaInvokeMethod.Invoke(lambda, new object[] { testObject, list });

testObject.Dump();

Dumping object(MyObject)
 MyField  : null
Dumping object(MyObject)
 MyField  : []

see: https://dotnetfiddle.net/FZN3iX 参见: https : //dotnetfiddle.net/FZN3iX

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM