C#中反射和泛型的高级技巧

作者:微信公众号:【架构师老卢】
10-4 10:20
115

高级软件开发人员使用泛型和反射来节省编写数百行代码的工作。有一系列的技巧已经使用了多年,但不幸的是,它们并不广为人知。以下是其中的一些技巧。

1. 使用 lambda 表达式获取属性名称

这样做的原因很简单。如果你对属性名称进行硬编码,然后更改它们,你可能会在不知不觉中破坏它。使用此技巧,您可以安全地更改属性名称。

// Instead of  
// var nameProp = "Name";   
// you can use   
var nameProp = PropertyName((Foo i) => i.Name);  
  
  
public static string PropertyName<Entity, Prop>(  
 Expression<Func<Entity, Prop>> expression)  
{  
    MemberExpression member = expression.Body as MemberExpression;  
  
    return member.Member.Name;  
}

2. 轻松映射对象

在对象之间映射公共属性是业务软件中的一项常见任务。通过使用泛型和反射,您可以在执行此任务时节省大量时间和代码。

下面是一个简化的映射器类示例

public class Mapper<TSource, TTarget>  
    where TSource : class  
    where TTarget : class  
{  
    public void Map(TSource source, TTarget target)  
    {  
        var typeSource = typeof(TSource);  
  
        var properties = typeSource.GetProperties();  
  
        // loop each property  
        foreach (var sourceProperty in properties)  
        {  
            CopyValues(source, sourceProperty, target);  
        }  
    }  
  
    public void CopyValues(TSource source, PropertyInfo sourceProperty, TTarget target)  
    {  
        var typeTarget = typeof(TTarget);  
        var targetProperty = typeTarget.GetProperty(sourceProperty.Name);  
  
        if (targetProperty == null) return;  
  
        object value = sourceProperty.GetValue(source, null);  
        targetProperty.SetValue(target, value, null);  
    }  
}

现在,无需手动映射所有常见属性,如下所示

var foo = new Foo();  
var bar = new Bar();  
  
bar.Name = foo.Name;  
bar.Name1 = foo.Name1;  
bar.Name2 = foo.Name2;  
bar.Name3 = foo.Name3;

您只需按上面所示调用 mapper:

var mapper = new Mapper<Foo, Bar>();  
mapper.Map(foo, bar);

3. 从类外部调用私有方法

信不信由你,即使方法被标记为私有,它仍然可以从其声明外部调用。我不确定这是否是一个安全漏洞,但您应该注意这一点,并考虑其他方法来保护您的方法。除此之外,您可以使用反射来访问和使用这些方法。

考虑这个带有私有 a 方法的 class

public class PrivateMethodClass   
{  
    private void GetPrivateThings()  
    {  
        Console.WriteLine("This is private");  
    }   
}

您可以通过这种方式调用 private 方法

var instance = new PrivateMethodClass();  
var type = typeof(PrivateMethodClass);  
  
var privateMethod = type.GetMethod("GetPrivateThings", BindingFlags.NonPublic | BindingFlags.Instance);  
  
privateMethod.Invoke(instance, null);

4. 集合扩展

是的,我们喜欢 LINQ,而且,您可以轻松地扩展它。例如,假设您需要一个方法,该方法从两个特定日期之间的列表中返回元素。

您可以执行以下操作

public static class CollectionExtensions  
{  
    public static IEnumerable<T> Between<T>(  
 this IEnumerable<T> source,   
 Func<T, DateTime> getDate,   
 DateTime from,  
 DateTime to)  
    {  
  
        return source.Where(entity =>   
            getDate(entity) >= from &&   
            getDate(entity) <= to);  
    }  
}

并像这样使用它

var dates = new List<Foo>() {  
    new Foo() { Date = new DateTime(2024, 1, 1)},  
    new Foo() { Date = new DateTime(2024, 1, 2)}  
};  
  
var from = new DateTime(2024, 1, 1);  
var to = new DateTime(2024, 1, 1);  
  
var filtered = dates.Between(x => x.Date, from, to);

5. 在运行时创建复杂表达式

假设我们想过滤一个具有特定条件的列表

var fooList = new List<Foo>() {  
 // elements  
};  
   
var res = fooList  
  .Where(i => i.Condition1 == "value1"  
            && i.Condition2 == "value2"  
            && i.Condition3 == "value2"  
            && i.Condition4 == "value4"  
        );

这样做的问题在于,过滤条件 Expression 必须在编译时编写。在运行时创建条件的另一种方法是使用反射构建 Expression。

构建表达式的常用方法是如下所示的类

public class ExpressionBuilder<T>
{

    // creates expresion entity => entity.propertyName == value
    public Expression CreatePropertyExpression(
      Expression entityParameter, 
      string propertyName, object? value)
    {
        var property = typeof(T).GetProperty(propertyName);
        var propertyAccess = Expression.MakeMemberAccess(entityParameter, property);
        var valueExpression = Expression.Constant(value, property.PropertyType);
        var equalsExpression = Expression.Equal(propertyAccess, valueExpression);

        return equalsExpression;
    }
    
     // creates expresion entity => entity.propertyName1 == value1
     //                            && entity.propertyName1 == value1
     //                            && entity.propertyNameN == valueN
    public Func<T, bool> ComplexExpression(
        List<Tuple<string, object>> conditions
        )
    {
        Expression expression = null;
        var parameter = Expression.Parameter(typeof(T), "entity");

        foreach (var condition in conditions)
        {
            var currentExpression = CreatePropertyExpression(
              parameter, 
              condition.Item1, 
              condition.Item2);

            if (expression == null)
            {
                expression = currentExpression;
                continue;
            }

            expression = Expression.AndAlso(expression, currentExpression);
        }

        var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);

        return lambda.Compile();
    }
}

你可以像这样使用它

var builder = new ExpressionBuilder<Foo>();
   
// this can be create it at runtime     
var condition = new List<Tuple<string, object>>() {
   new Tuple<string, object>("Condition1", "Value1"),
   new Tuple<string, object>("Condition2", "Value2"),
     // etc
};

var expression = builder.ComplexExpression(condition);
var filtered = list.Where(expression).ToList();
相关留言评论
昵称:
邮箱:
阅读排行