2010-06-06 138 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是一個引用類型,而不是一個值類型。 – 2010-06-07 13:08:34

回答

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 2010-06-07 13:09:09

3

你需要使用反射來調用方法,像這樣:

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

然而,你的方法不應該擺在首位通用的;它應該只需要兩個參數object。 (或者,如果它是通用的,它應該緩存泛型類型的屬性和委託)

+0

順便說一下,您可以使用Expression而不是Reflection來提高性能。 – 2010-06-06 14:12:04

+0

@丹尼:但只有當你緩存表達式。 (在泛型類型的靜態字段中)另外,通過創建委託可以獲得更好的性能。 – SLaks 2010-06-06 14:21:04