C# 使用反射动态创建类的 getter 和 setter 的动态委托

3

我正在使用这段 .Net 反射代码在运行时生成动态类实例。

https://www.c-sharpcorner.com/UploadFile/87b416/dynamically-create-a-class-at-runtime/

我正在使用.Net反射创建对象列表,这些对象具有动态的属性集,因为我正在从Excel文件中读取输入,该文件根据业务需求可能具有动态列。但是,我在做很多循环来获取GetType().GetProperty(""),这会降低性能。我正在尝试为从GetType().GetProperties()获得的PropertiesInfo[]动态委托它。
下面是运行时类的Property1的静态getter和setter委托。
Action<MyClass, int> setter = (Action<MyClass, int>)Delegate.CreateDelegate(typeof(Action<MyClass, int>), null, typeof(MyClass).GetProperty("Property1").GetSetMethod());  

Func<MyClass, int> getter = (Func<MyClass, int>)Delegate.CreateDelegate(typeof(Func<MyClass, int>), null, typeof(MyClass).GetProperty("Property1").GetGetMethod());

我希望能够使我的类中每个创建的属性都具有动态性。我陷入困境,不确定是否可以使用任何Linq MemberExpression 来实现它。

有人能帮我吗?那将是非常好的。


你是否知道编译时类属性的类型?如果不知道,你能接受使用 objectdynamic 吗? - vzwick
1个回答

2
这是一个相当基本的解决方案,针对每种类型简单地缓存Getters/Setters。
public static class CachedPropertyAccessUtilsFactory
{
    /*
     * Convenience Factory to avoid creating instances of
     * CachedPropertyAccessUtils by reflection
     */
    public static CachedPropertyAccessUtils<TWrapped> Create<TWrapped>(
        TWrapped instance)
    {
        return new CachedPropertyAccessUtils<TWrapped>(instance);
    }
}

public class CachedPropertyAccessUtils<TWrapped>
{
    private readonly TWrapped _instance;

    public CachedPropertyAccessUtils(TWrapped instance)
    {
        _instance = instance;
    }

    public GetSetWrapper<TProperty> Property<TProperty>(string propertyName)
    {
        return new GetSetWrapper<TProperty>(_instance, propertyName);
    }

    public class GetSetWrapper<TProperty>
    {
        /*
         * Caches generated getters/setters by property name.
         * Since this field is static it is shared between all instances with
         * identical TWrapped and TProperty.
         */
        private static readonly ConcurrentDictionary<string, GetterAndSetterTuple> GettersAndSettersByPropertyName
            = new ConcurrentDictionary<string, GetterAndSetterTuple>();

        private readonly TWrapped _instance;
        private readonly string _propertyName;

        public GetSetWrapper(TWrapped instance, string propertyName)
        {
            _instance = instance;
            _propertyName = propertyName;

            // Create a Getter/Setter pair if none has been generated previously
            GettersAndSettersByPropertyName.GetOrAdd(propertyName, _ => new GetterAndSetterTuple() {
                Getter = (Func<TWrapped, TProperty>)Delegate
                    .CreateDelegate(typeof(Func<TWrapped, TProperty>),
                        null,
                        typeof(TWrapped)
                            .GetProperty(propertyName)
                            .GetGetMethod()),
                Setter = (Action<TWrapped, TProperty>)Delegate
                    .CreateDelegate(typeof(Action<TWrapped, TProperty>),
                        null,
                        typeof(TWrapped)
                            .GetProperty(propertyName)
                            .GetSetMethod())
            });
        }

        public TProperty GetValue()
        {
            return GettersAndSettersByPropertyName[_propertyName].Getter(_instance);
        }

        public GetSetWrapper<TProperty> SetValue(TProperty value)
        {
            GettersAndSettersByPropertyName[_propertyName].Setter(_instance, value);
            return this;
        }

        class GetterAndSetterTuple
        {
            public Func  <TWrapped, TProperty> Getter { get; set; }
            public Action<TWrapped, TProperty> Setter { get; set; }
        }
    }
}

用法示例:

var myInstance = SomeCodeToCreateATypeAtRuntimeAndCreateAnInstanceOfIt();

var wrappedInstance = CachedPropertyAccessUtilsFactory.Create(myInstance);

// The first call to Property() will generate the corresponding Getter/Setter
wrappedInstance.Property<int>("Property1").SetValue(99);

// Subsequent calls will use the cached Getter/Setter
wrappedInstance.Property<int>("Property1").GetValue(); // => 99

// The property can be conveniently held on to:
var property1 = wrappedInstance.Property<int>("Property1");

property1.SetValue(-1);
property1.GetValue(); // => -1

当然,这假设您在运行时知道属性类型,以便可以切换到正确的Property<TProperty>()调用。

如果您没有这些信息,则可以添加另一层间接层,通过反射将string propertyName映射到包装类型上的相应属性,并缓存查找结果。
在这种情况下,返回的GetSetWrapper当然必须支持带有object作为返回/参数类型的GetValue/SetValue,这将涉及幕后一些强制转换/装箱操作。


感谢提供的路线图。我已经从父类“RuntimeClass”派生了我的运行时类,并尝试分配委托。由于我需要指定<TWrapped>泛型类型。我无法传递我的类型,因为它是在运行时发出的。但是我遇到了一个异常:“无法绑定到目标方法,因为其签名或安全透明度与委托类型不兼容”。对此有什么想法吗?@vzwick - Hari Hara Krishnan
我的意思是,我的运行时生成的类型“Mappings”是从名为RuntimeClass的类型派生而来的。我指的是协变。 - Hari Hara Krishnan
@Hari Hara Krishnan,你有没有解决运行时类的问题?我也在处理同样的问题,vzwick提出的解决方案并没有改善我的情况。我开了这个问题,希望能得到一些帮助:https://dev59.com/O2gMtIcB2Jgan1znH1Od - Carlos Moreira

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接