Как я могу перегрузить метод С# конкретными экземплярами универсального типа
-
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>
, и просто требует от меня пересылки конструкторов.