2009-12-15 3 views
36

Je code:délégué Cast à Func en C#

public delegate int SomeDelegate(int p); 

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

Je peux jeter Inc-SomeDelegate ou Func<int, int>:

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

mais je ne peux pas jeter Inc-SomeDelegate et après CAST à Func<int, int> de la manière habituelle comme ceci:

Func<int, int> c = (Func<int, int>)a; // Сompilation error 

Comment je peux le faire?

Répondre

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

est court pour

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

Vous ne pouvez pas lancer une instance de SomeDelegate à un Func < int, int > pour la même raison que vous ne pouvez pas convertir une chaîne en dictionnaire < int, int > - ce sont des types différents.

Cela fonctionne:

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

qui est du sucre syntaxique pour

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

Peut-être souligner le fait que les trucs personnalisés que vous avez écrit est normalement fait par la magie du compilateur. – Dykam

+0

+1 pour la bonne explication. Il y a un moyen plus simple que * Func c = x => a (x); * cependant - voir ma réponse. –

+1

Je présume que dans le dernier bit 'public Foo' aurait dû être' public MyLambda'? – Chris

23

Essayez ceci:

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

Jetez un oeil à ma réponse pour un moyen plus facile d'y parvenir. –

+0

Laide mais utile si vous ne pouvez pas se permettre l'indirection de dire 'Func c = b.Invoke;'. Mais il y a une chose à retenir: Dans .NET, tous les types de délégués sont des délégués "multicast", et le code ci-dessus ne prendra que la méthode ** last ** dans la liste d'invocation. Si vous ne pouvez pas garantir que la liste d'invocation n'a qu'un seul membre, vous devrez l'itérer, je suppose. –

+0

@JeppeStigNielsen Je ne pense pas qu'un 'Func' multicast ait beaucoup de sens. Quelle valeur de retour utiliseriez-vous? –

7

Le problème est que:

SomeDelegate a = Inc; 

est-ce pas en fait un casting. C'est la forme abrégée de:

SomeDelegate a = new SomeDelegate(Inc); 

Par conséquent, il n'y a pas de conversion. Une solution simple à votre problème peut être ce (en C# 3,0)

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

Le problème avec cela est que vous êtes en train d'encapsuler le délégué avec un nouveau qui utilise une méthode anonyme, ce qui a un coût. –

+2

Oui, vous avez raison. Code élégant vs. Performance. Cela dépend de ce que vous avez besoin. – Gamlor

4

Il est le même genre de problème que cela:

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

qui est le même genre de problème comme:

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

Les objets ne peuvent pas être castés d'un type à un autre type sans rapport, même si les types sont complètement compatibles. En l'absence d'un meilleur terme: un objet a une identité de type qu'il transporte au moment de l'exécution. Cette identité ne peut pas être modifiée après la création de l'objet. La manifestation visible de cette identité est Object.GetType().

52

Il y a beaucoup plus simple de le faire, que toutes les autres réponses ont manqué:

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

Voir this blog post pour plus d'informations.

+3

Nice. C'est similaire à la solution de Gamlor, mais sans la méthode anonyme. Toujours, il enveloppe le délégué d'origine, contrairement à la solution que je propose. –

+7

Si quelqu'un n'est pas sûr de ce que Diego veut dire, jetez un oeil aux propriétés Target et Method du délégué 'a' d'origine et au délégué 'c'. Avec le mécanisme de Diego, 'c' pointe directement vers la méthode originale, tout comme 'a'. Avec la méthode de Winston, elle ne pointe pas - elle pointe vers le délégué qui pointe à son tour vers la méthode d'origine, de sorte que vous obtenez un niveau supplémentaire d'indirection inutile. –

+2

Comme Diego et Ian l'ont mentionné, cette solution enveloppe l'original du délégué original. Par conséquent, si vous utilisez cette solution avec des événements, vous ne pourrez pas vous désabonner. Avec la solution de Diego, vous pouvez. – Verax

7

Cela fonctionne (en C# 4.0 au moins - pas essayé dans les versions antérieures):

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

Si vous regardez l'IL, cette compile dans exactement le même code que la réponse de Winston. Voici l'IL pour la deuxième ligne de ce que je viens d'écrire:

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

Et c'est aussi précisément ce que vous voyez si vous attribuez a.Invoke en c. Incidemment, bien que la solution de Diego soit plus efficace, en ce sens que le délégué résultant se réfère directement à la méthode sous-jacente plutôt que de passer par l'autre délégué, il ne gère pas correctement les délégués de multidiffusion. La solution de Winston, parce qu'elle se contente de renvoyer complètement à l'autre délégué. Si vous voulez une solution directe qui gère également les délégués avec des cibles multiples, vous avez besoin quelque chose d'un peu plus complexe:

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

Cela fait la bonne chose pour les délégués avec une seule cible par la façon dont-il finira par produire seulement un délégué unique du type cible qui fait directement référence à la méthode (et, le cas échéant, à l'objet) référencée par le délégué d'entrée.

+0

Je me demande pourquoi les 'Method' et' Target' d'un délégué multi-appel à l'une de ses cibles. Je pense qu'il aurait été plus logique d'avoir le point 'Target' du délégué à lui-même, et de faire pointer' Method' vers sa méthode 'Invoke' [qui vérifierait si elle invoquait elle-même et, si oui, utilise la multidiffusion list] ou une méthode "invoke multicast". Cela aurait évité le risque de transformer accidentellement un délégué multi-appel en un appel unique. – supercat

+0

Toute la situation autour de 'MulticastDelegate' est un gâchis, car Microsoft a changé d'avis sur la façon de gérer cela assez tard dans la journée. Dans le premier aperçu public de .NET, certains délégués étaient en multidiffusion et d'autres non. Ils ont finalement décidé d'abandonner cette distinction mais n'ont pas vraiment eu le temps de nettoyer les choses, ce qui a laissé quelques anomalies dans la hiérarchie des types de délégués. –

4

Vous pouvez pirater une distribution en utilisant une astuce dans laquelle vous utilisez l'équivalent C# d'une union C++. La partie difficile est la structure avec deux membres qui ont un [FieldOffset (0)]:

[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

La première fois que j'ai entendu parler de ce hack merveilleusement dangereux. Merci! – Ashe