Динамически устанавливаемый аргумент универсального типа - PullRequest
6 голосов
/ 06 июня 2010

Исходя из моего вопроса здесь , я пытаюсь создать общий компаратор равенства значений. Раньше я никогда не играл с отражением, поэтому не уверен, что нахожусь на правильном пути, но в любом случае у меня пока есть эта идея:

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 в рекурсивном вызове. Можно ли вообще сделать это динамически?

Здесь есть пара связанных с этим вопросов, которые я прочитал, но я не смог достаточно их понять, чтобы понять, как они могут применяться в моей ситуации.

Ответы [ 2 ]

6 голосов
/ 06 июня 2010

Вы можете избежать размышлений при вызове, если будете рады сравнить на основе статически известных типов свойств.

Это основано на выражениях в 3.5 для простого отражения, возможно сделать это лучше, чтобы уменьшить усилия для чрезвычайно вложенных типов, но это должно подойти для большинства потребностей.

Если вам нужно работать с типами среды выполнения, потребуется некоторый уровень отражения (хотя это будет дешево, если вы снова кешируете методы доступа и сравнения для каждого свойства), но это по своей природе намного сложнее, поскольку типы среды выполнения для под-свойств может не совпадать, поэтому для полной общности вы должны учитывать следующие правила:

  • считают несовпадающие типы НЕ равными
    • простой для понимания и простой в реализации
    • вряд ли будет полезной операцией
  • В тот момент, когда типы расходятся, используйте стандартную реализацию EqualityComparer<T>.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();
    }
}
4 голосов
/ 06 июня 2010

Вам нужно вызвать метод, используя отражение, например:

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

Однако ваш метод не должен быть универсальным; он должен просто принять два object параметра. (Или, если он универсальный, он должен кэшировать свойства и делегаты в универсальном типе)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...