Как я могу перегрузить метод C # конкретными экземплярами универсального типа - PullRequest
1 голос
/ 01 января 2009

Исходя из фона 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
    }
}

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

Edit: Надеюсь, это немного понятнее.

Ответы [ 5 ]

3 голосов
/ 01 января 2009

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

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

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

2 голосов
/ 02 января 2009

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

1 голос
/ 02 января 2009

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

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;
}
0 голосов
/ 02 января 2009

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

Заменить 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>, а просто требует от меня пересылки конструкторов.

0 голосов
/ 01 января 2009

Редактировать: я не уверен, что вы можете завершить это, как вы пытаетесь. Я попробовал всевозможные уловки, чтобы попытаться заставить это работать, и не могу заставить это скомпилировать. Лучшее, что я могу сделать, - вытащить вызов метода за пределы моего общего класса. Если ваш вызов метода находится за пределами, то вы можете конкретно определить, что 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 и один для строки), и все это работает хорошо ... после?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...