Динамично установите общий аргумент типа

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

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

    else 
    {
        IEnumerable 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, p2) )
                return false;
        }
    }
    return true;
}

Это не компилирует, потому что я не могу разработать, как установить тип T в рекурсивном вызове. Действительно ли возможно сделать это динамично вообще?

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

6
задан Community 23 May 2017 в 12:04
поделиться

2 ответа

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

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

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

  • считать несовпадающие типы НЕ равными
    • проста для понимания и проста в реализации
    • вряд ли будет полезной операцией
  • В точке, где типы расходятся, используйте стандартную реализацию EqualityComparer .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();
    }
}
6
ответ дан 10 December 2019 в 00:34
поделиться

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

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

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

4
ответ дан 10 December 2019 в 00:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: