2010-06-06 19 views
5

In seguito alla mia domanda here, sto cercando di creare un comparatore di uguaglianza di valore generico. Non ho mai giocato con la riflessione, prima quindi non so se sono sulla strada giusta, ma in ogni caso ho avuto questa idea finora:Dinamicamente impostato argomento tipo generico

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; 
} 

Questo non viene compilato perché non posso lavorare fuori come impostare il tipo di T nella chiamata ricorsiva. È possibile farlo dinamicamente?

Ci sono un paio di domande correlate qui che ho letto ma non ho potuto seguirle abbastanza per capire come potrebbero applicarsi nella mia situazione.

+0

Non dovrebbe essere (t1 è ValueType | t1 è stringa)? Con || se la prima condizione fallisce, la seconda non viene testata. System.String è un tipo di riferimento, non un tipo di valore. –

risposta

6

È possibile evitare la riflessione sull'invocazione se si desidera confrontare in base ai tipi di proprietà staticamente conosciuti.

Questo fa affidamento su Expressions in 3.5 per eseguire il one off reflection in modo semplice, è possibile farlo meglio per ridurre lo sforzo per tipi estremamente annidati, ma questo dovrebbe andare bene per la maggior parte delle esigenze.

Se è necessario lavorare sui tipi di runtime, sarà necessario un certo livello di riflessione (anche se sarebbe conveniente se si memorizza di nuovo la cache per ogni accesso di proprietà e metodi di confronto) ma questo è intrinsecamente molto più complesso poiché i tipi di runtime su sub proprietà possono non corrispondere così, per la piena generalità si dovrebbe prendere in considerazione le regole come la seguente:

  • considerano tipi non corrispondenti a non essere uguale
    • semplice da capire e facile da implementare
    • non è probabile che essere un'operazione utile
  • Nel punto i tipi divergono utilizzare il EqualityComparer<T>.Default implementazione standard sui due FattorialeMenoUno ulteriore
    • ancora semplice, po 'più difficile da implementare.
  • considerare uguali se hanno un sottoinsieme di proprietà comuni che sono essi stessi uguali
    • complicato, non proprio terribilmente significativo
  • considerano uguali se condividono lo stesso sottoinsieme di proprietà (sulla base di nome e tipo) che sono uguali
    • complicato, voce in Duck Typing

Ci sono una varietà di altre opzioni, ma questo dovrebbe essere spunto di riflessione sul perché l'analisi runtime completa è difficile.

(notare che io si 'foglia' guardia di terminazione sono cambiata per essere quello che io ritengo di essere superiore, se si desidera utilizzare solo tipo Sting/valore per qualche ragione si sentono liberi)

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); 
    } 
} 

per i test Ho usato quanto segue:

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

grazie per una risposta molto dettagliata e infomativa. – fearofawhackplanet

3

è necessario chiamare il metodo utilizzando la riflessione, in questo modo:

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

Tuttavia, il metodo non deve essere generico, in primo luogo; dovrebbe semplicemente prendere due parametri object. (Oppure, se è generico, dovrebbe memorizzare nella cache proprietà e delegati in un tipo generico)

+0

È possibile migliorare le prestazioni utilizzando Expression anziché Reflection. –

+0

@Danny: Ma solo se si memorizza l'espressione nella cache. (In un campo statico in un tipo generico) Inoltre, è possibile ottenere prestazioni ancora migliori creando un delegato. – SLaks

Problemi correlati