2016-03-22 11 views
1

Dies ist eine Erweiterung von this question, die eine Antwort hatte, die in diesem speziellen Fall funktioniert.Typ Inferenz zwischen Generika mit umgekehrten Einschränkungen

Meine eigentliche Code sieht mehr wie folgt aus:

public abstract class BaseComparable<TLeft, TRight> 
{ } 

public class LeftComparable<TLeft, TRight> : BaseComparable<TLeft, TRight> where TLeft : IComparable<TRight> 
{ 
    public LeftComparable(TLeft value) { } 
} 

public class RightComparable<TLeft, TRight> : BaseComparable<TLeft, TRight> where TRight : IComparable<TLeft> 
{ 
    public RightComparable(TLeft value) { } 
} 

Wenn Sie die äquivalente Reflexion Code zu verwenden, was ich geschrieben, es funktioniert super:

public static BaseComparable<TLeft, TRight> AsComparableFor<TLeft, TRight>(this TLeft left, TRight right) 
{ 
    if (left is IComparable<TRight>) 
    { 
     var constructor = 
      typeof(LeftComparable<,>).MakeGenericType(typeof(TLeft), typeof(TRight)) 
             .GetConstructor(new[] { typeof(TLeft) }); 
     if (constructor != null) 
     { 
      return (BaseComparable<TLeft, TRight>)constructor.Invoke(new object[] { left }); 
     } 
    } 
    if (right is IComparable<TLeft>) 
    { 
     var constructor = 
      typeof(RightComparable<,>).MakeGenericType(typeof(TLeft), typeof(TRight)) 
             .GetConstructor(new[] { typeof(TLeft) }); 
     if (constructor != null) 
     { 
      return (BaseComparable<TLeft, TRight>)constructor.Invoke(new object[] { left }); 
     } 
    } 
    throw new ArgumentException(); 
} 

Dann können Sie

sagen
class Baz 
{ 
    public int Value { get; set; } 
} 
class Bar : IComparable<Baz> 
{ 
    public int Value { get; set; } 
    int IComparable<Baz>.CompareTo(Baz other) 
    { 
     return Value.CompareTo(other.Value); 
    } 
} 

// .... 

var bar = new Bar { Value = 1 }; 
var baz = new Baz { Value = 1 }; 
var compBaz = baz.AsComparableFor(bar); 
var compBar = bar.AsComparableFor(baz); 

Fantastisch, Typ Inferenz funktioniert genau wie erwartet.

Die Anpassung der akzeptierte Antwort oben, jedoch

public static class Comparable 
{ 
    public static BaseComparable<TLeft, TRight> 
        AsComparableFor<TLeft, TRight>(this IComparable<TRight> left, TRight right) 
    where TLeft : IComparable<TRight> 
    { 
     if (left is TLeft) 
     { 
      if (left is IComparable<TRight>) 
      { 
       return new LeftComparable<TLeft, TRight>((TLeft)left); 
      } 
     } 

     throw new InvalidCastException(); 
    } 

    public static BaseComparable<TLeft, TRight> 
        AsComparableFor<TLeft, TRight>(this TLeft left, IComparable<TLeft> right) 
    where TRight : IComparable<TLeft> 
    { 
     if (left is TLeft) 
     { 
      if (right is IComparable<TLeft>) 
      { 
       return new RightComparable<TLeft, TRight>((TLeft)left); 
      } 
     } 

     throw new InvalidCastException(); 
    } 
} 

Sie erfordert die Typargumente explizit angeben:

//bar.AsComparableFor(baz); 
//baz.AsComparableFor(bar); //Does not compile 

bar.AsComparableFor<Bar, Baz>(baz); 
baz.AsComparableFor<Baz, Bar>(bar); // Does compile 

Ein großer Teil davon war die Bibliothek so schmerzlos zu machen, wie möglich, und ich habe das Gefühl, dass ich die Art der Niederlagen etwas spezifizieren muss.

Gibt es einen Mittelweg? Kann ich den saubereren, reflexionsfreien Code von der akzeptierten Antwort mit der Typrückschlussstärke des Originals erhalten?

Edit: full code can be found in this gist.

+0

Wie können Sie zum Kompilieren die zu vergleichenden Eigenschaften ("int Value") kennen, wissen aber nur zur Laufzeit, welche Klassen Sie vergleichen werden? Dies scheint wie eine Trennung. Es scheint, als könnten Sie Ihre vergleichbare Klasse im Voraus konstruieren, anstatt ihre Informationen zur Laufzeit einzufügen. Plus, diese Art der Überprüfung zu tun ist hässlich. Vielleicht ist das nicht vermeidbar, aber yikes. Ich wünschte, du könntest das noch konkreter machen. Warum die Notwendigkeit, unterschiedliche Klassen zu vergleichen? – ErikE

+0

@ErikE '[Left | Right] Comparable' und' Comparable' sind Bibliotheksklassen. 'Bar' und' Baz' sind nur Beispiele dafür, was Benutzercode wäre. – RoadieRich

Antwort

1

Kann ich den Reiniger, reflexions Code aus der akzeptierten Antwort mit der Typinferenz Stärke des Originals erhalten?

Sie können nicht. Tatsächlich ist die akzeptierte Antwort nicht gut, weil es Werttyp Boxen beinhaltet.

Also wieder, Sie können Reflexion nicht vermeiden. Was können Sie tun, ist aber zu minimieren die Reflexion durch die gleiche Technik wie in EqualityComparer<T>.Defaultimplementation, Comparer<T>.Default usw. Der einzige Unterschied wäre, dass stattdessen eine einzelne Instanz zu schaffen, haben wir eine Singleton Fabrik Delegaten werden:

public abstract class BaseComparable<TLeft, TRight> 
{ 
    public static readonly Func<TLeft, BaseComparable<TLeft, TRight>> Factory = CreateFactory(); 
    private static Func<TLeft, BaseComparable<TLeft, TRight>> CreateFactory() 
    { 
     Type genericTypeDefinition; 
     if (typeof(IComparable<TRight>).IsAssignableFrom(typeof(TLeft))) 
      genericTypeDefinition = typeof(LeftComparable<,>); 
     else if (typeof(IComparable<TLeft>).IsAssignableFrom(typeof(TRight))) 
      genericTypeDefinition = typeof(RightComparable<,>); 
     else 
      throw new ArgumentException(); 
     var parameter = Expression.Parameter(typeof(TLeft), "value"); 
     var body = Expression.New(genericTypeDefinition 
      .MakeGenericType(typeof(TLeft), typeof(TRight)) 
      .GetConstructor(new[] { typeof(TLeft) }), parameter); 
     var lambda = Expression.Lambda<Func<TLeft, BaseComparable<TLeft, TRight>>>(body, parameter); 
     return lambda.Compile(); 
    } 
} 


public static class BaseComparable 
{ 
    public static BaseComparable<TLeft, TRight> AsComparableFor<TLeft, TRight>(this TLeft left, TRight right) 
    { 
     return BaseComparable<TLeft, TRight>.Factory(left); 
    } 
} 
+0

Gibt es irgendeinen Grund, dies über meine Refection-Version zu bevorzugen, außer "So funktioniert das Framework" - was an sich schon einen guten Grund haben muss? – RoadieRich

+0

Leistung. Es ist eine einmalige Reflektion pro -Paar. –

+0

Ah, also ist es implizit Caching/Memoizing ... das war etwas, das ich in Erwägung ziehen wollte. – RoadieRich

Verwandte Themen