有一个Collection可以将实现保存到具有多种类型的抽象泛型类中

时间:2019-05-30 17:49:54

标签: c# generics collections abstract-class

我有这个抽象类和实现,具有输入类型和输出类型:

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 :(得分:0)

您不能完全按照自己的意愿去做。

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(从技术上讲,任何实现都可以期望任何其他类型),那么您可以将什么值传递给集合中的每个项目可以与所有人一起工作吗?另外,由于每个返回值可能具有不同的类型,您将如何处理这些返回值?

您可以通过将输入和输出声明为object来解决对其进行 compile 的问题,尽管这样做会破坏使它通用的目的。您只需用此替换您的基类...

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

...但是那不是一个非常有用的类。

但是真正的问题是,接收和返回object的方法通常也不是非常有用。类型系统使我们能够知道参数,返回值,变量等的类型。这是指示代码意图的一部分。例如,我们编写这样的方法:

bool IsAnagram(string input)

因为我们想知道 if bool)是 input string)是一个字谜。知道这样做是我们调用它的原因。如果我们不想知道string是否是一个字谜,就不要调用此方法,因为我们不需要它。

但是如果我们有这样的方法怎么办:

object GetResult(object input)

没有理由存在。我们为什么要称呼它?如果我们有某个类型的值,并且想要对其进行处理并得到一些结果,我们将编写一个执行该操作的方法。

类似地,如果我们从方法中得到object,我们将如何处理呢?我们不知道这是什么。有点像去商店买东西,除了我们没什么想买的东西,他们递给我们一个包裹,我们不知道里面有什么。因为我们不知道会得到什么,所以我们没有计划要做什么。所以我们只是不这样做。

仅在不关心类型是什么的特定情况下使用object才有意义。这些情况不太常见,因为要使我们的应用程序执行任何操作,几乎总是需要使用指定的类型进行操作-创建它们,从某个地方获取它们,将它们传递给其他方法以及获取更多的预期类型作为回报。


如果您只想收集事物的集合,并且它们都采用不同的参数并返回不同的类型,则必须根据其最小公分母来声明该集合,即{ {3}},没有参数,也没有返回值。

您可以这样做:

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的集合很有用。但是在大多数情况下,没有理由采取一堆无关的动作来做不同的事情,并将它们彼此放在一个集合中。

答案 1 :(得分:0)

不能。至少没有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,这将不起作用。