参考属性列表

List of properties by ref

我正在尝试概括一个 C# class,它拥有多个相同类型的属性(并且它们都具有相同的实现)。

我最终想要的是删除所有属性并保留一个 Dictionary<string, type>,它使用唯一 ID 映射每个 属性。

目前删除属性会非常费力,但可以重构一些现有功能,而不是多次“复制粘贴”到 read/update 一个 属性 ,对未来字典使用循环并更新每个键。

我怎样才能做到以下几点?

//Simplified example
class Person {

    public double Height { get; set; }
    public double Weight { get; set; }
    public double Age { get; set; }
    public double SomethingElse { get; set; }
    //.. maybe more

    public void CopyPasteCode()
    {
        Height = -1.0;
        Weight = -1.0;
        Age = -1.0;
        SomethingElse = -1.0;
    }

    public void Refactored()
    {
        var properties = //How to do this?
            new List<ref double>() 
            {
                ref Height, ref Weight, ref Age, ref IQ
            };
        foreach(var p in properties)
        {
            p = -1.0;
        }
    }
}

我不会重点说明为什么您可能需要这种方法,而是展示一些实现技术。

选项 1:反射,字符串 属性 名称

使用这个辅助方法:

static void SetProperties(object obj, string[] propertyNames, object value)
{
    var type = obj.GetType();

    foreach (var name in propertyNames)
    {
        var property = type.GetProperty(name);
        property.SetValue(obj, value);        
    }
}

你这样做:

public void Refactored()
{
    var propertyNames = new[] { 
       nameof(Height), nameof(Weight), nameof(Age), nameof(IQ) 
    };
    SetProperties(this, propertyNames, -0.1m);
}

选项 2:反射,强类型,用 lambdas 指定的属性

辅助方法:

static void SetProperties<TObj, TProp>(
    TObj obj, 
    TProp value, 
    params Expression<Func<TObj, TProp>>[] properties)
{
    foreach (var lambda in properties)
    {
        var property = (PropertyInfo)((MemberExpression)lambda.Body).Member;
        property.SetValue(obj, value);        
    }
}

然后你按如下方式使用它:

public void Refactored()
{
    SetProperties(
        this, 
        -0.1m,
        x => x.Height, x => x.Weight, x => x.Age, x => x.IQ);
}

使用 lambda 具有编译器类型和名称检查、重命名重构以及使用 IntelliSense 选择属性(当您键入 x => x. 时)的优势。

选项 3:Reflection.Emit

通过反射访问属性意味着性能损失,这取决于您的要求。提供与原始 CopyPasteCode() 相同性能的更快方法是使用从 IL 指令构建的动态方法,您可以即时创建一次,然后在应用程序的整个生命周期中使用。

但是,如果您是 Reflection.Emit 的新手:这是一种低级机制,需要大量学习。出于这个原因,使用可用的包装器库之一会更简单、更快(也更安全)。在这种情况下,Marc Gravell 的 fast-member 将是一个不错的选择。

结合之前的选项:

static void SetProperties<TObj, TProp>(
    TObj obj, 
    TProp value, 
    params Expression<Func<TObj, TProp>>[] properties)
{
    var accessor = TypeAccessor.Create(obj.GetType()); 

    foreach (var lambda in properties)
    {
        var property = (PropertyInfo)((MemberExpression)lambda.Body).Member;
        accessor[obj, property.Name] = value;      
    }
}

用法不变:

public void Refactored()
{
    SetProperties(
        this, 
        -0.1m,
        x => x.Height, x => x.Weight, x => x.Age, x => x.IQ);
}