2015-05-11 6 views
8

Wir haben eine generische Erweiterungsmethode, die nur für Objekte funktioniert, die sowohl die INotifyCollectionChanged- als auch die IEnumerable-Schnittstelle unterstützen. Es ist wie folgt geschrieben:Wie können Sie eine Variable vom Typ 'Objekt' explizit in eine generische Mehrfacheinschränkung umwandeln?

public static class SomeExtensions 
{ 
    public static void DoSomething<T>(this T item) 
    where T : INotifyCollectionChanged, IEnumerable 
    { 
     // Do something 
    } 
} 

Die folgende Fein kompiliert seit ObservableCollection<string> beide Schnittstellen implementiert und dank der ‚var‘ Schlüsselwort wird Knowntype stark typisiert:

var knownType = new ObservableCollection<string>(); 
knownType.DoSomething(); 

aber wir versuchen, diese zu nennen von einem ValueConverter. Das Problem ist, dass der eingehende Wert vom Typ object ist und obwohl wir testen können, dass das übergebene Objekt beide Schnittstellen implementiert, können wir nicht herausfinden, wie wir es explizit umwandeln, so dass wir diese generische Erweiterungsmethode aufrufen können. Das funktioniert nicht (natürlich) ...

object unknownType = new ObservableCollection<string>(); 
((INotifyCollectionChanged,IEnumerable)unknownType).DoSomething(); 

Wie können Sie ein Objekt mehrere Schnittstellen werfen, so dass Sie die generische anrufen können? Nicht einmal sicher, dass es möglich ist.

+1

, dass eine fantastische Frage ist. Sie können natürlich 'dynamisch' verwenden, aber ich bezweifle, dass Sie danach suchen. – usr

+0

Sie könnten Reflexion verwenden. – mageos

+1

Um Dynamik zu vermeiden, werden nur die Typen verwendet, die beide Schnittstellen erfüllen. Sie könnten eine Klasse oder Schnittstelle verwenden, die beide Schnittstellen implementiert. Wie Sie jedoch festgestellt haben, gibt es in C# keine Möglichkeit, den Typ einer Variablen darzustellen, die zwei nicht verwandte Schnittstellen erfüllt. – recursive

Antwort

2

Der sauberste Weg, ich könnte dies tun, wäre dynamic. Da Erweiterungsmethoden jedoch nicht mit dynamic (siehe bleich) kompatibel sind, müssen Sie explizit auf die Erweiterungsmethode verweisen.

Extensions.DoSomething(unknownType as dynamic); 

EDIT Als Warnung aginst die Gotcha ich in hier lief, beachten Sie, dass die Methode mit expliziten dyanmic als Typargument Aufruf (über DoSomething<dynamic>) wird nicht Arbeit - es verursacht Fehler kompilieren, wenn sie versuchen, Übereinstimmung mit mehreren Constraints. Wenn Sie keine Mehrfachbedingungen verwenden, führt dies außerdem dazu, dass dynamic auf der Grundlage des Kompilierzeittyps der übergebenen Variablen nicht der Laufzeittyp aufgelöst wird.

Dies führt zu einem Aufruf an Extensions.UnknownType<dynamic>, dessen dynamic zur Laufzeit auflösen wird - was bedeutet, dass es den vollständig abgeleiteten Typ des gegebenen Parameters verwenden wird. Solange dieser Parameter die gewünschten Schnittstellen implementiert, kann es losgehen.

Seien Sie vorsichtig wie, wie viel dynamic Code, dies könnte Probleme auftreten, die bis zur Laufzeit nicht gesehen werden. Verwenden Sie sparsam!

Wenn Sie mehrere Anrufe mit dem gleichen generischen paremeters machen, könnten Sie eine generische Hilfsmethode in Ihrem Konverter besser dran, das Hinzufügen und dann dass Aufruf mit value as dynamic

Nachtrag:

Bei der Verwendung von dynamic, wird alles, was gegen das Objekt dynamic aufgerufen wird, versuchen, als ein Mitglied des gegebenen Typs zur Laufzeit aufzulösen, wird aber nicht Lookup-Erweiterungsmethoden, da sie inhärent in einem völlig anderen existieren Klasse, oft eine andere Versammlung.

+1

Nicht sicher, ich folge der Argumentation hier, weil, wie Sie sagten, Dynamiken nicht Erweiterungsmethoden aufrufen. Und wenn Sie diesen Weg trotzdem gehen, warum erweitern Sie nicht einfach "Object" und testen die beiden Schnittstellen explizit nach innen, indem Sie bei Bedarf eine ArgumentException werfen? Natürlich würde ich empfehlen, "Object" nicht auf diese Route auszudehnen, ich würde wahrscheinlich "dieses" einfach fallen lassen und es aus den oben genannten Gründen sowieso zu einer statischen Methode machen. – MarqueIV

+1

Erweiterungsmethoden können, wenn sie explizit aufgerufen werden, mit 'dynamischen' Objekten verwendet werden. Während dies ausführlicher ist, erlaubt dies Aufrufe von Methoden, die mehrere Schnittstellenbeschränkungen haben. – David

+0

Ich denke, du wirst die Abstimmung bekommen, weil wir damit fertig waren, obwohl es, wie ich erwartet hatte, keine Möglichkeit gibt, eine harte Besetzung zu machen. Das heißt, ich denke deine zweite Lösung (nach dem 'oder') ist falsch. Das hat sich für uns nicht ergeben. Hast du das zur Arbeit bekommen? Wenn ja, können Sie ein Beispiel geben? Wenn nicht, entferne es einfach und ich werde deine als die Antwort markieren. – MarqueIV

0

Sie können versuchen, mithilfe von MethodInfo und Delegaten einen Delegaten für die richtige Methode abzurufen.

Die folgende Lösung dynamische nicht Gebrauch macht, aber ein wenig von Reflexion. Grundsätzlich ist die Methode führt die folgenden Operationen:

  1. Holt ein Delegierter der Methode, die Sie aufrufen möchten, aber für einen anderen, bekannten Typ.
  2. Ruft die generische Methodendefinition ab.
  3. Ruft dann die MethodInfo für den Objekttyp ab.
  4. Ruft die Erweiterungsmethode auf, indem das letzte MethodInfo aufgerufen wird.

Die _Placeholder Klasse ist der bekannte Typ verwendet, um die Anfangs Delegierten zu holen, aber Sie können jede andere Art verwenden, die die allgemeinen Bedingungen erfüllen.

Auf diese Weise haben Sie auch eine Compile-Time-Prüfung des Namens der Erweiterungsmethode und ihrer Einschränkungen.

Ein guter Ansatz würde auch die endgültige MethodInfo (oder Delegate) zwischenspeichern, um teure Wiederholungen zu vermeiden.


private sealed class _Placeholder : INotifyCollectionChanged, IEnumerable 
{ 
    public event NotifyCollectionChangedEventHandler CollectionChanged; 

    public IEnumerator GetEnumerator() { throw new NotImplementedException(); } 
} 

static void Test(object obj) 
{ 
    if ((obj is INotifyCollectionChanged) && (obj is IEnumerable)) 
    { 
     var typeObject = obj.GetType(); 

     // Retrieve the delegate for another type 
     Action<_Placeholder> _DoSomething = SomeExtensions.DoSomething<_Placeholder>; 
     // Retrieve the generic definition 
     var infoTemp = _DoSomething.Method.GetGenericMethodDefinition(); 
     // Retrieve the MethodInfo for our object's type 
     var method = infoTemp.MakeGenericMethod(typeObject); 

     // Call the extension method by invoking 
     method.Invoke(null, new[] { obj }); 

     //If you can return the delegate, you can try the following: 
     //var typeDelegate = typeof(Action<>).MakeGenericType(typeObject); 
     //var action = Delegate.CreateDelegate(typeDelegate, method); 
     //((Action<_Test>)action)(obj as _Test); 
    } 
} 
Verwandte Themen