¿Cómo puedo sobrecargar un método C # por instancias específicas de un tipo genérico?

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

  •  03-07-2019
  •  | 
  •  

Pregunta

Viniendo de un fondo C ++, me he encontrado con un problema con sobrecarga basada en una instancia específica de un tipo genérico. Lo siguiente no funciona ya que solo se genera una vez la instancia del código para la clase Foo<T>, por lo que dentro de Method, el tipo de this es simplemente Foo<A>, no Foo<B> o <=> como esperaba En C ++ estoy acostumbrado a crear instancias de plantillas como tipos únicos.

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

¿Hay alguna manera de hacer que algo como esto funcione en C #? Prefiero no tener que duplicar el código en Foo como clases separadas para cada tipo para el que quiero usarlo.

Editar: Esperemos que esto sea un poco más claro.

¿Fue útil?

Solución

Esto funciona para el caso estático. Tratar con funciones de instancia sería un poco más complicado. Esta publicación de Jon Skeet podría proporcionar una forma razonable de tratar con métodos de instancia.

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

Otros consejos

Ahora tengo una pieza de código genuinamente completa que demuestra el problema. Nota para OP: intente compilar su código antes de publicarlo. Había un montón de cosas que tenía que hacer para llegar tan lejos. Es bueno hacerlo lo más fácil posible para que otras personas lo ayuden. También he eliminado un montón de partes extrañas. OtherFoo no es realmente relevante aquí, ni FooBase lo es.

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

Sí, esto no se compila. ¿Qué esperabas que hiciera exactamente? Tenga en cuenta que la resolución de sobrecarga se realiza en tiempo de compilación , no en tiempo de ejecución. Como dice fallen888, podría lanzar y llamar al método sobrecargado apropiado, pero ¿cuál de las dos sobrecargas esperaría que el compilador elija de otra manera? ¿Qué quiere que haga con Foo<string> en lugar de Foo<A> o Foo<B>?

Todo esto demuestra que los genéricos .NET son significativamente diferentes de las plantillas de C ++, por supuesto ...

No lo he intentado pero parece que deberías poder lograr lo que quieres haciendo A & amp; B visitable (por ejemplo, con el patrón de visitante acíclico).

Editar: no estoy seguro de que pueda completar esto mientras lo intenta. He intentado todo tipo de trucos para intentar que esto funcione y no puedo hacer que se compile. Lo mejor que puedo hacer es extraer la llamada al método fuera de mi clase genérica. Si su llamada al método está fuera, puede definir específicamente qué T está en el genérico. Sin embargo, dentro del método, en el momento de la compilación, el compilador no sabe cuál será T, por lo que no sabe a qué método sobrecargado llamar. La única forma en que puedo ver esto es usar un interruptor para determinar el tipo de T y especificar manualmente la sobrecarga para llamar.

Lo mejor que puedo hacer es esto, que no es exactamente lo que buscas, pero podría usarse con un efecto similar:

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

y obtuvo salida:

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

Tengo cuatro funciones sobrecargadas que hacen referencia a dos clases genéricas de referencia (una para int y otra para string) y dos tipos regulares de referencia (uno para int y otro para string) y todo funciona bien ... es esto lo que usted estás después?

Editar: El problema no parece ser la invocación de los métodos sobrecargados, sino que tiene que ver con su foreach, que está tratando de convertir todos los elementos de la lista al mismo tipo que el primero para hacer referencia al método sobrecargado El primer elemento que no se ajuste a esa definición exacta hará que su compilación falle.

Esperaba encontrar una manera más fácil de hacer esto, pero por ahora voy con esto:

Reemplace la clase Foo<T> con estas clases:

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

Y cambie las instancias a

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

Esto al menos no requiere duplicar el código en <=>, y solo requiere que reenvíe los constructores.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top