Есть ли коллекция, которая содержит реализации абстрактного универсального класса с несколькими типами - PullRequest
0 голосов
/ 30 мая 2019

У меня есть этот абстрактный класс и реализации, которые имеют тип Input и тип Output:

public abstract class Action<TInput, TOutput> 
{
    public Action() {}

    public virtual TOutput Execute(TInput input)
    {
        throw new NotImplementedException();
    }
}

public class Foo<int, string> : Action<int, string>
{
    public override string Execute(int i)
    {
        ...
    }
}

public class Bar<string, int> : Action<string, int>
{
    public override int Execute(string s)
    {
        ...
    }
}

Как я могу держать этих бесов? в коллекцию и цикл через них?

как:

actions.Add(new Foo());
actions.Add(new Bar());

object obj;

foreach(var item in actions)
    obj = item.Execute(obj);

Ответы [ 2 ]

0 голосов
/ 30 мая 2019

Вы не можете.По крайней мере, с Execute оставшимся безопасным типом.
Что вы можете сделать, это создать другой базовый класс или интерфейс, который объявляет метод Execute, который получает и возвращает object:

public interface IAction {
    object Execute(object input);
}

public abstract class Action<TInput, TOutput> : IAction {
    public object Execute(object input) {
        return Execute((TInput) input);
    }

    public virtual TOutput Execute(TInput input)
    {
        throw new NotImplementedException();
    }
}

Это будет работать (будет компилироваться):

List<IAction> actions = new List<IAction>();
actions.Add(new Foo());
actions.Add(new Bar());

object obj;

foreach(var item in actions)
    obj = item.Execute(obj);

Однако!Имейте в виду, что это скомпилируется, но приведет к ошибке времени выполнения:

IAction action = new Foo(); // Expects an integer
action.Execute("I'm not an integer");

Строка будет приведена к объекту, а затем к int, что не будет работать.

0 голосов
/ 30 мая 2019

Вы не можете делать то, что пытаетесь сделать.

actions.Add(new Foo()); // expects and returns string
actions.Add(new Bar()); // expects and returns int

object obj;

foreach(var item in actions)
    obj = item.Execute(obj);

Если Foo ожидает string, а Bar ожидает int (и технически любая реализация может ожидать любой другой тип), то какое значение вы можете передать каждому элементу в коллекции, который будетработать со всеми из них?Кроме того, что бы вы сделали с возвращаемыми значениями, поскольку каждое из них могло бы быть разного типа?

Вы можете решить проблему получения compile , объявив входы и выходы какobject, хотя это лишило бы смысла сделать его общим.Вы бы просто заменили свой базовый класс на этот ...

public abstract class Action 
{
    public abstract object Execute(object input);
}

... но это не очень полезный класс.

Но настоящая проблема в том, что методы, которые получают и возвращают object, не , как правило, , также очень полезны.Система типов позволяет нам знать типы аргументов, возвращаемых значений, переменных и т. Д. Это часть того, что указывает на намерения нашего кода.Например, мы пишем метод, подобный этому:

bool IsAnagram(string input)

, потому что мы хотим знать , если (bool), то input (string) равенанаграмма.Знание того, что оно делает, и есть наша причина для этого.Если мы не хотим знать, является ли string анаграммой, мы не вызываем этот метод, потому что он нам не нужен.

Но что, если у нас есть такой метод:

object GetResult(object input)

У него нет оснований для существования.Почему мы бы это назвали?Если у нас есть значение некоторого типа, и мы хотим что-то с ним сделать и получить какой-то результат, мы напишем метод, который это делает.

Аналогично, если мы вернемся object из метода, чтомы делаем с этим?Мы не знаем что это.Это немного похоже на поход в магазин, чтобы что-то купить, за исключением того, что мы не имеем в виду, что мы хотим купить, и они вручают нам посылку, а мы не знаем, что внутри.У нас не могло быть никаких планов относительно того, что мы намеревались сделать, когда мы получили это, так как мы не знали, что мы получим.Так что мы просто не будем этого делать.

Имеет смысл использовать object только в определенных сценариях, где нам все равно, что это за тип.Эти сценарии встречаются реже, потому что для того, чтобы наше приложение могло что-либо делать, ему почти всегда нужно что-то делать с указанными типами - создавать их, получать их откуда-то, передавать их другим методам и получать больше ожидаемых типов взамен.


Если вы просто хотите, чтобы коллекция делала , и все они принимали разные аргументы и возвращали разные типы, тогда вы должны объявить коллекцию в соответствии с наименьшим общим знаменателем, которыйэто просто Action без аргументов и без возвращаемых значений.

Вы можете сделать это:

var actions = new List<Action>();

actions.Add(() =>
{
    var foo = new Foo();
    foo.Execute(5);
});

actions.Add(() =>
{
    var bar = new Bar();
    bar.Execute("Hello!");
});

foreach (var action in actions)
{
    action.Invoke();
}

... и могут быть случаи, когда коллекцияAction полезно.Но в большинстве случаев нет причин предпринимать кучу несвязанных действий, которые делают разные вещи, и помещать их в коллекцию друг с другом.

...