Как я могу перегрузить метод С# конкретными экземплярами универсального типа

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

  •  03-07-2019
  •  | 
  •  

Вопрос

Имея опыт работы с C++, я столкнулся с проблемой перегрузки на основе конкретного экземпляра универсального типа.Следующее не работает, поскольку только один экземпляр кода для Foo<T> класс когда-либо генерируется, поэтому внутри Method, тип this это просто Foo<T>, нет Foo<A> или Foo<B> как я и надеялся.В C++ я привык к тому, что шаблоны создаются как уникальные типы.

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

Есть ли способ заставить что-то подобное работать на С#?Я бы предпочел не дублировать код в Foo в виде отдельных классов для каждого типа, для которого я хочу его использовать.

Редактировать:Надеюсь, это стало немного яснее.

Это было полезно?

Решение

Это работает для статического случая.Работа с функциями экземпляра была бы немного сложнее.Этот сообщение от Джона Скита может предоставить разумный способ работы с методами экземпляра.

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

Другие советы

Теперь у меня есть действительно законченный фрагмент кода, демонстрирующий проблему.Примечание для ОП:пожалуйста, попробуйте скомпилировать свой код, прежде чем публиковать его.Мне пришлось сделать кучу вещей, чтобы зайти так далеко.Хорошо, если другим людям будет как можно проще помочь вам.Еще я удалил кучу лишнего.OtherFoo здесь не имеет особого значения, равно как и 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);
    }
}

Да, это не компилируется.Чего именно вы от него ожидали?Имейте в виду, что разрешение перегрузки выполняется при время компиляции, а не время выполнения.Как говорит Fall888, вы можете привести и вызвать соответствующий перегруженный метод, но какую из двух перегрузок, по вашему мнению, компилятор выберет в противном случае?С чем вы хотите это сделать? Foo<string> вместо Foo<A> или Foo<B>?

Все это, конечно, демонстрирует, что дженерики .NET действительно существенно отличаются от шаблонов C++...

Я не пробовал, но кажется, что вы сможете добиться того, чего хотите, сделав A и B доступными для посещения (например,с шаблоном ациклического посетителя).

Редактировать:Я не уверен, что вы сможете завершить это так, как пытаетесь.Я пробовал всевозможные трюки, чтобы заставить это работать, но не смог его скомпилировать.Лучшее, что я могу сделать, — это вывести вызов метода за пределы моего универсального класса.Если ваш вызов метода находится снаружи, вы можете конкретно определить, что такое T в общем коде.Однако внутри метода, во время компиляции, компилятор не знает, каким будет T, поэтому он не знает, какой перегруженный метод вызывать.Единственный способ обойти эту проблему — использовать переключатель для определения типа T и вручную указать перегрузку для вызова.

Лучшее, что я могу сделать, это это, это не совсем то, что вам нужно, но его можно использовать для аналогичного эффекта:

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

и получил вывод:

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

У меня есть четыре перегруженные функции, ссылающиеся на два ссылающихся на универсальные классы (один для int и один для строки) и две ссылающиеся на обычные типы (один для int и один для строки), и все работает нормально...это то, что тебе нужно?

Редактировать:Кажется, проблема не в вызове перегруженных методов, а в вашем foreach, который пытается преобразовать все элементы в списке в тот же тип, что и первый, чтобы ссылаться на перегруженный метод.Первый элемент, который не соответствует этому точному определению, приведет к сбою компиляции.

Я надеялся найти более простой способ сделать это, но сейчас я воспользуюсь этим:

Заменять Foo<T> класс с этими классами:

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

И измените экземпляры на

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

Это, по крайней мере, не требует дублирования кода в Foo<T>, и просто требует от меня пересылки конструкторов.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top