【问题标题】:Dynamically set generic type argument动态设置泛型类型参数
【发布时间】:2011-02-28 09:15:01
【问题描述】:

继我的问题here 之后,我正在尝试创建一个通用的值相等比较器。我以前从未玩过反射,所以不确定我是否走在正确的轨道上,但无论如何我到目前为止已经有了这个想法:

bool ContainSameValues<T>(T t1, T t2)
{
    if (t1 is ValueType || t1 is string)
    {
        return t1.Equals(t2);
    }

    else 
    {
        IEnumerable<PropertyInfo> properties = t1.GetType().GetProperties().Where(p => p.CanRead);
        foreach (var property in properties)
        {
            var p1 = property.GetValue(t1, null);
            var p2 = property.GetValue(t2, null);

            if( !ContainSameValues<p1.GetType()>(p1, p2) )
                return false;
        }
    }
    return true;
}

这不会编译,因为我不知道如何在递归调用中设置 T 的类型。是否可以动态地执行此操作?

我已经阅读了一些相关的问题,但我无法充分了解它们以了解它们如何适用于我的情况。

【问题讨论】:

  • 不应该是(t1 是 ValueType | t1 是字符串)吗?与 ||如果第一个条件失败,则不测试第二个条件。 System.String 是引用类型,而不是值类型。

标签: c# .net reflection types


【解决方案1】:

如果您乐于根据静态已知的属性类型进行比较,则可以避免对调用进行反思。

这依赖于 3.5 中的表达式以一种简单的方式进行一次性反射,可以更好地执行此操作以减少极度嵌套类型的工作量,但这对于大多数需求来说应该没问题。

如果您必须处理运行时类型,则需要一定程度的反射(尽管如果您再次缓存每个属性的访问和比较方法,这会很便宜)但这本质上要复杂得多,因为子属性上的运行时类型可能不匹配,为了全面起见,您必须考虑以下规则:

  • 认为不匹配的类型不相等
    • 简单易懂且易于实施
    • 不太可能是有用的操作
  • 此时类型分歧使用标准的EqualityComparer&lt;T&gt;.Default 实现两者,不再递归
    • 同样简单,但实施起来有些困难。
  • 如果它们有一个共同的属性子集,它们本身是相等的,则考虑相等
    • 复杂,不是很有意义
  • 如果它们共享相同的属性子集(基于名称和类型)且它们本身是相等的,则考虑相等
    • 复杂,进入 Duck Typing

还有许多其他选项,但这应该是关于为什么完整的运行时分析很难进行思考的食物。

(请注意,我已将您的“叶子”终止保护更改为我认为更好的东西,如果您出于某种原因只想使用 sting/value 类型,请随意)

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


class StaticPropertyTypeRecursiveEquality<T>
{
    private static readonly Func<T,T, bool> actualEquals;

    static StaticPropertyTypeRecursiveEquality()
    {
        if (typeof(IEquatable<T>).IsAssignableFrom(typeof(T)) || 
            typeof(T).IsValueType ||
            typeof(T).Equals(typeof(object)))
        {
            actualEquals = 
                (t1,t2) => EqualityComparer<T>.Default.Equals(t1, t2);
        }
        else 
        {
            List<Func<T,T,bool>> recursionList = new List<Func<T,T,bool>>();
            var getterGeneric = 
                typeof(StaticPropertyTypeRecursiveEquality<T>)
                    .GetMethod("MakePropertyGetter", 
                        BindingFlags.NonPublic | BindingFlags.Static);
            IEnumerable<PropertyInfo> properties = typeof(T)
                .GetProperties()
                .Where(p => p.CanRead);
            foreach (var property in properties)                
            {
                var specific = getterGeneric
                    .MakeGenericMethod(property.PropertyType);
                var parameter = Expression.Parameter(typeof(T), "t");
                var getterExpression = Expression.Lambda(
                    Expression.MakeMemberAccess(parameter, property),
                    parameter);
                recursionList.Add((Func<T,T,bool>)specific.Invoke(
                    null, 
                    new object[] { getterExpression }));                    
            }
            actualEquals = (t1,t2) =>
                {
                    foreach (var p in recursionList)
                    {
                        if (t1 == null && t2 == null)
                            return true;
                        if (t1 == null || t2 == null)
                            return false;
                        if (!p(t1,t2))
                            return false;                            
                    }
                    return true;
                };
        }
    }

    private static Func<T,T,bool> MakePropertyGetter<TProperty>(
        Expression<Func<T,TProperty>> getValueExpression)
    {
        var getValue = getValueExpression.Compile();
        return (t1,t2) =>
            {
                return StaticPropertyTypeRecursiveEquality<TProperty>
                    .Equals(getValue(t1), getValue(t2));
            };
    }

    public static bool Equals(T t1, T t2)
    {
        return actualEquals(t1,t2);
    }
}

为了测试,我使用了以下内容:

public class Foo
{
    public int A { get; set; }
    public int B { get; set; }
}

public class Loop
{
    public int A { get; set; }
    public Loop B { get; set; }
}

public class Test
{
    static void Main(string[] args)
    {
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<String>.Equals(
            "foo", "bar"));
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<Foo>.Equals(
            new Foo() { A = 1, B = 2  },
            new Foo() { A = 1, B = 2 }));
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<Loop>.Equals(
            new Loop() { A = 1, B = new Loop() { A = 3 } },
            new Loop() { A = 1, B = new Loop() { A = 3 } }));
        Console.ReadLine();
    }
}

【讨论】:

    【解决方案2】:

    你需要使用反射来调用方法,像这样:

    MethodInfo genericMethod = typeof(SomeClass).GetMethod("ContainSameValues");
    MethodInfo specificMethod = genericMethod.MakeGenericMethod(p1.GetType());
    if (!(bool)specificMethod.Invoke(this, new object[] { p1, p2 }))
    

    但是,您的方法首先不应该是通用的;它应该只需要两个object 参数。 (或者,如果它是泛型的,它应该在泛型类型中缓存属性和委托)

    【讨论】:

    • 顺便说一句,您可以使用表达式而不是反射来提高性能。
    • @Danny:但前提是你缓存了表达式。 (在泛型类型的静态字段中)此外,您可以通过创建委托获得更好的性能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    相关资源
    最近更新 更多