2010-06-06 5 views
5

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

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

+0

Не должно быть (t1 является ValueType | t1 является строкой)? С || если первое условие не выполняется, второе не тестируется. System.String - это ссылочный тип, а не тип значения. –

ответ

6

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

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

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

  • рассмотреть несогласованные типы нЕ быть равны
    • легко понять и легко осуществить
    • скорее всего, не быть полезная операция
  • В точке типов расходятся использовать стандартную EqualityComparer<T>.Default реализацию на два и рекурсию не далее
    • снова просто, несколько сложнее реализовать.
  • считают равными, если они имеют общий набор свойств, которые сами по себе являются равными
    • сложно, на самом деле не очень значимым
  • считают равными, если они имеют то же подмножество свойств (на основе название и тип), которые сами по себе равны
    • сложный, направляясь в утиную печать

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

(обратите внимание, что я изменил тебе «лист» прекращение охраны, то, что я считаю, чтобы быть выше, если вы хотите просто использовать тип жала/значение по какой-то причине не стесняйтесь)

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(); 
    } 
} 
+0

спасибо за очень подробный и информативный ответ. – fearofawhackplanet

3

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

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

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

+0

Btw вы можете улучшить производительность, используя выражение вместо отражения. –

+0

@ Danny: Но только если вы кешируете выражение. (В статическом поле в родовом типе) Кроме того, вы можете получить еще лучшую производительность, создав делегат. – SLaks

Смежные вопросы