2009-12-15 17 views
36

Ich habe Code:Guss Delegaten Func in C#

public delegate int SomeDelegate(int p); 

public static int Inc(int p) { 
    return p + 1; 
} 

warf ich kann Inc-SomeDelegate oder Func<int, int>:

SomeDelegate a = Inc; 
Func<int, int> b = Inc; 

aber ich kann nicht Inc-SomeDelegate und nach dieser Besetzung zu gieße Func<int, int> mit der üblichen Weise wie folgt:

Wie kann ich es tun?

Antwort

43
SomeDelegate a = Inc; 
Func<int, int> b = Inc; 

für

SomeDelegate a = new SomeDelegate(Inc); // no cast here 
Func<int, int> b = new Func<int, int>(Inc); 

kurz ist, kann Sie nicht eine Instanz von SomeDelegate zu einem Func < int, int gegossen > aus dem gleichen Grund können Sie keine Zeichenfolge zu einem Dictionary < int, int > zu werfen - sie sind verschiedene Arten.

Dies funktioniert:

Func<int, int> c = x => a(x); 

die für syntaktischer Zucker ist

class MyLambda 
{ 
    SomeDelegate a; 
    public MyLambda(SomeDelegate a) { this.a = a; } 
    public int Invoke(int x) { return this.a(x); } 
} 

Func<int, int> c = new Func<int, int>(new MyLambda(a).Invoke); 
+0

Vielleicht betonen Sie die Tatsache, dass die benutzerdefinierten Sachen, die Sie geschrieben haben, normalerweise durch Compiler-Magie erledigt werden. – Dykam

+0

+1 für die nette Erklärung. Es gibt einen einfacheren Weg als * Func c = x => a (x); * obwohl - siehe meine Antwort. –

+1

Ich nehme an, im letzten Bit hätte 'public Foo'' public MyLambda' sein sollen? – Chris

23

Versuchen Sie folgendes:

Func<int, int> c = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), 
                  b.Target, 
                  b.Method); 
+0

Werfen Sie einen Blick auf meine Antwort für eine einfachere Möglichkeit, dies zu erreichen. –

+0

Hässlich, aber nützlich, wenn Sie nicht die Umleitung von 'Func c = b.Invoke;'. Aber es gibt eine Sache zu erinnern: In .NET sind alle Delegate-Typen "Multicast" Delegaten, und der obige Code wird nur die ** letzte ** -Methode in der Aufrufliste nehmen. Wenn Sie nicht garantieren können, dass die Aufrufliste nur ein Mitglied hat, müssen Sie es wahrscheinlich durchlaufen. –

+0

@JeppeStigNielsen Ich denke nicht, dass ein Multicast 'Func' viel Sinn macht. Welchen Rückgabewert würden Sie verwenden? –

7

Das Problem ist, dass:

SomeDelegate a = Inc; 

Ist das nicht eigentlich eine Besetzung. Es ist die Kurzform von:

SomeDelegate a = new SomeDelegate(Inc); 

Daher gibt es keine Besetzung. Eine einfache Lösung für Ihr Problem kann sein, diese (in C# 3,0)

Func<int,int> f = i=>a(i); 
+1

Das Problem dabei ist, dass Sie den Delegaten mit einem neuen umhüllen, der eine anonyme Methode verwendet, die Kosten verursacht. –

+2

Ja, du hast Recht. Eleganter Code vs. Leistung. Kommt drauf an, du brauchst was du wählst. – Gamlor

4

Es ist die gleiche Art von Problem wie folgt aus:

public delegate int SomeDelegate1(int p); 
public delegate int SomeDelegate2(int p); 
... 
    SomeDelegate1 a = new SomeDelegate1(Inc); 
    SomeDelegate2 b = (SomeDelegate2)a; // CS0030 

, die die gleiche Art von Problem ist, wie:

public class A { int prop { get; set; } } 
public class B { int prop { get; set; } } 
... 
    A obja = new A(); 
    B objb = (B)obja; // CS0029 

Objekte können nicht von einem Typ in einen nicht verwandten anderen Typ umgewandelt werden, obwohl die Typen ansonsten vollständig kompatibel sind. Mangels eines besseren Begriffs: Ein Objekt hat eine Typidentität, die es zur Laufzeit mit sich führt. Diese Identität kann nicht geändert werden, nachdem das Objekt erstellt wurde. Die sichtbare Manifestation dieser Identität ist Object.GetType().

52

Es gibt eine viel einfachere Art und Weise, es zu tun ist, was alle anderen Antworten verpasst haben:

Func<int, int> c = a.Invoke; 

this blog post Info für mehr sehen.

+3

Schön. Es ist ähnlich wie Gamlor's Lösung, aber ohne die anonyme Methode. Noch wickelt es den ursprünglichen Delegaten, im Gegensatz zu meiner vorgeschlagenen Lösung. –

+7

Wenn jemand nicht sicher ist, was Diego meint, werfen Sie einen Blick auf die Ziel- und Methodeneigenschaften des ursprünglichen 'a' Delegaten und des 'c' Delegaten. Mit Diego's Mechanismus zeigt 'c' direkt auf die ursprüngliche Methode, genau wie 'a'. Mit Winstons Methode ist dies nicht der Fall - es verweist auf den Delegaten, der wiederum auf die ursprüngliche Methode verweist, sodass Sie eine unnötige zusätzliche Ebene der Indirektion erhalten. –

+2

Wie Diego und Ian erwähnten, umschließt diese Lösung das Original des ursprünglichen Delegaten. Wenn Sie diese Lösung mit Ereignissen verwenden, können Sie sich daher nicht abmelden. Mit Diegos Lösung können Sie. – Verax

7

Dies funktioniert (in C# 4.0 zumindest - in früheren Versionen nicht versucht):

SomeDelegate a = Inc; 
Func<int, int> c = new Func<int, int>(a); 

Wenn man sich die IL suchen, diese kompiliert in genau den gleichen Code wie Winston Antwort. Hier ist die IL für die zweite Zeile von dem, was ich gerade geschrieben habe:

ldloc.0 
ldftn  instance int32 ConsoleApplication1.Program/SomeDelegate::Invoke(int32) 
newobj  instance void class [mscorlib]System.Func`2<int32,int32>::.ctor(object, native int) 

Und das ist auch genau das, was Sie sehen, wenn Sie a.Invoke in c zuweisen.

Übrigens, obwohl Diegos Lösung effizienter ist, da der resultierende Delegat direkt auf die zugrunde liegende Methode verweist und nicht durch den anderen Delegaten geht, behandelt er Multicastdelegaten nicht korrekt. Winstons Lösung tut das, weil sie sich nur komplett auf den anderen Delegierten verlagert. Wenn Sie eine direkte Lösung wollen, die auch Teilnehmer mit mehreren Zielen behandelt, müssen Sie etwas ein wenig komplexe:

public static TResult DuplicateDelegateAs<TResult>(MulticastDelegate source) 
{ 
    Delegate result = null; 
    foreach (Delegate sourceItem in source.GetInvocationList()) 
    { 
     var copy = Delegate.CreateDelegate(
      typeof(TResult), sourceItem.Target, sourceItem.Method); 
     result = Delegate.Combine(result, copy); 
    } 

    return (TResult) (object) result; 
} 

Dies ist das Richtige für die Teilnehmer mit einem einzigen Ziel durch die Art und Weise-es produziert wird am Ende nur ein einzelner Delegat des Zieltyps, der direkt auf die Methode (und ggf. das Objekt) verweist, auf die sich der betreffende Eingabe-Delegat bezieht.

+0

Ich frage mich, warum die 'Methode' und' Target' eines Delegierten mit mehreren Anrufen auf eines seiner Ziele zeigen. Ich würde denken, dass es sinnvoller wäre, wenn der "Target" der Delegierten auf sich selbst zeigt und "Method" auf seine "Invoke" -Methode zeigt [die prüft, ob sie sich selbst aufgerufen hat und, falls ja, das Multicast verwendet Liste] oder eine "invoke multicast" -Methode. Damit wäre das Risiko vermieden worden, versehentlich einen Delegierten mit mehreren Anrufen in einen Einzelanruf zu verwandeln. – supercat

+0

Die ganze Situation um 'MulticastDelegate' ist ein Durcheinander, weil Microsoft ihre Meinung geändert hat, wie man ziemlich spät am Tag damit umgeht. In der ersten öffentlichen Vorschau von .NET wurden einige Delegaten Multicast und andere nicht. Sie entschieden sich schließlich, diese Unterscheidung aufzugeben, hatten aber nicht wirklich Zeit, Dinge aufzuräumen, was einige Anomalien in der Hierarchie des Delegattyps hinterließ. –

4

Sie können einen Cast hacken, indem Sie einen Trick verwenden, bei dem Sie das C# -Aquivalent einer C++ - Union verwenden. Der schwierige Teil ist die Struktur mit zwei Mitgliedern, die ein [FieldOffset (0)] haben:

[TestFixture] 
public class Demo 
{ 
    public void print(int i) 
    { 
     Console.WriteLine("Int: "+i); 
    } 

    private delegate void mydelegate(int i); 

    [StructLayout(LayoutKind.Explicit)] 
    struct funky 
    { 
     [FieldOffset(0)] 
     public mydelegate a; 
     [FieldOffset(0)] 
     public System.Action<int> b; 
    } 

    [Test] 
    public void delegatetest() 
    { 
     System.Action<int> f = print; 
     funky myfunky; 
     myfunky.a = null; 
     myfunky.b = f; 

     mydelegate a = myfunky.a; 

     a(5); 
    } 
} 
+3

Erstmal habe ich von diesem wunderbar gefährlichen Hack gehört. Vielen Dank! – Ashe