Comment surcharger une méthode C # par des instances spécifiques d'un type générique

StackOverflow https://stackoverflow.com/questions/405429

  •  03-07-2019
  •  | 
  •  

Question

Venant d’un arrière-plan C ++, j’ai rencontré un problème de surcharge basé sur une instance spécifique d’un type générique. Ce qui suit ne fonctionne pas car une seule instance du code de la classe Foo<T> est jamais générée. Ainsi, dans Method, le type de this est simplement Foo<A>, et non Foo<B> ou <=> comme je l'avais espéré. En C ++, les modèles sont instanciés en tant que types uniques.

using System.Collections.Generic;
class A
{
    // Concrete class
}

class B
{
    // Concrete class
}

class Bar
{
    public void OverloadedMethod(Foo<A> a) {} // do some A related stuff
    public void OverloadedMethod(Foo<B> b) {} // do some B related stuff
    public void OverloadedMethod(OtherFoo of) {} // do some other stuff

     public void VisitFoo(FooBase fb) { fb.Method(this); }
}

abstract class FooBase
{
    public abstract void Method(Bar b);
}

class Foo<T> : FooBase
{
    // Class that deals with As and Bs in an identical fashion.
    public override void Method(Bar b)
    {
        // Doesn't compile here
        b.OverloadedMethod(this);
    }
}

class OtherFoo : FooBase
{
    public override void Method(Bar b)
    {
        b.OverloadedMethod(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<FooBase> ListOfFoos = new List<FooBase>();
        ListOfFoos.Add(new OtherFoo());
        ListOfFoos.Add(new Foo<A>());
        ListOfFoos.Add(new Foo<B>());

        Bar b = new Bar();
        foreach (FooBase fb in ListOfFoos)
            b.VisitFoo(fb);
        // Hopefully call each of the Bar::Overloaded methods
    }
}

Existe-t-il un moyen de faire en sorte que quelque chose comme cela fonctionne en C #? Je préférerais ne pas avoir à dupliquer le code dans Foo en tant que classes séparées pour chaque type pour lequel je veux l'utiliser.

Modifier: Espérons que cela soit un peu plus clair.

Était-ce utile?

La solution

Ceci fonctionne pour le cas statique. Traiter avec des fonctions d'instance serait un peu plus compliqué. Ce message de Jon Skeet pourrait constituer un moyen raisonnable de gérer les méthodes d'instance.

class Program
{
    static void Main(string[] args)
    {
        var testA = new Foo<A>();
        testA.Method();
        var testB = new Foo<B>();
        testB.Method();
        Console.ReadLine();
        var testString = new Foo<string>(); //Fails
        testString.Method(); 
        Console.ReadLine();
    }
}

class A { }
class B { }
class Bar
{
    public static void OverloadedMethod(Foo<A> a)
    {
        Console.WriteLine("A");
    }
    public static void OverloadedMethod(Foo<B> b)
    {
        Console.WriteLine("B");
    }
}
class Foo<T>
{
    static Foo()
    {
        overloaded = (Action<Foo<T>>)Delegate.CreateDelegate(typeof(Action<Foo<T>>), typeof(Bar).GetMethod("OverloadedMethod", new Type[] { typeof(Foo<T>) }));
    }

    public void Method()
    {
        overloaded(this);
    }

    private static readonly Action<Foo<T>> overloaded;
}

Autres conseils

J'ai maintenant un morceau de code véritablement complet qui illustre le problème. Note à OP: essayez de compiler votre code avant de le poster. Il y avait beaucoup de choses que je devais faire pour aller aussi loin. Il est bon que les autres personnes puissent vous aider aussi facilement que possible. J'ai également enlevé un tas de bits superflus. OtherFoo n'est pas vraiment pertinent ici, pas plus que FooBase.

class A {}
class B {}

class Bar
{
    public static void OverloadedMethod(Foo<A> a) { }
    public static void OverloadedMethod(Foo<B> b) { }
}

class Foo<T>
{
    // Class that deals with As and Bs in an identical fashion.
    public void Method()
    {
        // Doesn't compile here
        Bar.OverloadedMethod(this);
    }
}

Oui, cela ne compile pas. Qu'attendiez-vous que cela fasse exactement? Gardez à l'esprit que la résolution de la surcharge est effectuée au temps de compilation , pas au temps d'exécution. Comme indiqué par8888, vous pouvez lancer et appeler la méthode surchargée appropriée, mais laquelle des deux surcharges vous attendez-vous à ce que le compilateur choisisse autrement? Que voulez-vous que cela fasse avec Foo<string> au lieu de Foo<A> ou Foo<B>?

Tout cela montre que les génériques .NET sont bien différents des modèles C ++, bien sûr ...

Je ne l’ai pas essayé mais il semble que vous devriez être capable de réaliser ce que vous voulez en faisant un & ampli; B visitable (par exemple, avec le modèle de visiteur acyclique).

Éditer: Je ne suis pas sûr que vous puissiez compléter ceci en essayant. J'ai essayé toutes sortes de trucs pour essayer de faire fonctionner ceci et je ne peux pas le faire compiler. Le mieux que je puisse faire est d'extraire l'appel de méthode en dehors de ma classe générique. Si votre appel de méthode est extérieur, vous pouvez alors définir spécifiquement ce que T est dans le générique. Cependant, à l'intérieur de la méthode, au moment de la compilation, le compilateur ne sait pas ce que T sera, il ne sait donc pas quelle méthode surchargée appeler. La seule façon pour moi de comprendre cela consiste à utiliser un commutateur pour déterminer le type de T et spécifier manuellement la surcharge à appeler.

Le mieux que je puisse faire est le suivant, qui n'est pas tout à fait ce que vous recherchez, mais il pourrait être utilisé pour un effet similaire:

class Stuff<T>
{
    public T value { get; set; }
}

class Program
{
    static void DummyFunc(Stuff<int> inst)
    {
        Console.WriteLine("Stuff<int>: {0}", inst.value.ToString());
    }

    static void DummyFunc(Stuff<string> inst)
    {
        Console.WriteLine("Stuff<string>: {0}", inst.value);
    }
    static void DummyFunc(int value)
    {
        Console.WriteLine("int: {0}", value.ToString());
    }
    static void DummyFunc(string value)
    {
        Console.WriteLine("string: {0}", value);
    }
    static void Main(string[] args)
    {
        var a = new Stuff<string>();
        a.value = "HelloWorld";

        var b = new Stuff<int>();
        b.value = 1;

        var c = "HelloWorld";
        var d = 1;

        DummyFunc(a);
        DummyFunc(b);
        DummyFunc(c);
        DummyFunc(d);
    }
}

et obtenu la sortie:

Stuff<string>: HelloWorld
Stuff<int>: 1
string: HelloWorld
int: 1

J'ai quatre fonctions surchargées référençant deux classes génériques référençantes (une pour int et une pour chaîne) et deux types normaux référençant (une pour int et une pour chaîne) et tout fonctionne bien ... est-ce ce que vous êtes après?

Modifier: le problème ne semble pas être lié à l’appel des méthodes surchargées, mais à votre forfor qui tente de convertir tous les éléments de la liste dans le même type que le premier afin de référencer le méthode surchargée. Le premier élément qui ne correspond pas à cette définition exacte entraînera l’échec de votre compilation.

J'espérais trouver un moyen plus facile de faire cela, mais pour l'instant je vais avec ceci:

Remplacez Foo<T> la classe par les classes suivantes:

abstract class Foo<T> : FooBase
{
    // Class that deals with As and Bs in an identical fashion.
}

class Foo_A : Foo<A>
{
    public override void Method(Bar b)
    {
        b.OverloadedMethod(this);
    }
}

class Foo_B : Foo<B>
{
    public override void Method(Bar b)
    {
        // Doesn't compile here
        b.OverloadedMethod(this);
    }
}

Et changez les instanciations en

List<FooBase> ListOfFoos = new List<FooBase>();
ListOfFoos.Add(new OtherFoo());
ListOfFoos.Add(new Foo_A());
ListOfFoos.Add(new Foo_B());

Cela ne nécessite au moins pas de dupliquer le code dans <=>, mais simplement que je transmette les constructeurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top