C#通用工厂方法

5
也许这是一个简单的新手C#问题,但没关系——它会成为我其他问题的新起点,那些问题太难了,没有人知道答案。 :)
假设我在C#中有一个通用类型:
Thing<T>

假设我想使用静态工厂方法来制作一件东西。在Java中,这并不是问题:

public static <T> Thing<T> createThing()
{
  return flag ? new Thing<Integer>(5) : new Thing<String>("hello");
}

我应该如何在C#中实现这个?谢谢。


1
你到底想要实现什么目标?你的代码中从未使用过泛型类型参数 T - Adam Robinson
1
无论你做什么,这种构造在C#中是不可能的。首先,Thing<int>和Thing<string>是不同的类型,而且你只能在一个函数中返回单一类型。Java由于其类型擦除语义而没有这个问题。它最终仍然返回一个Thing。 - Jeff Mercado
@Jeff,这不是一个擦除问题;擦除发生在编译的结果中,我们在这里讨论的是编译期间发生的静态类型安全检查(无论它是否稍后被擦除)。此外,你所说的“不同类型”部分似乎忽略了C# 4中引入的泛型协变性。我认为这里真正的问题与C#中通配符类型的缺乏有关。 - Garret Wilson
@Adam,请阅读http://msdn.microsoft.com/en-us/library/dd799517.aspx并告诉我那是否是你的最终答案。显然,如果Thing<>是某些条件下的接口,则C#允许这种情况发生。 - Garret Wilson
@Garret:是的,这绝对是我的最终答案。在运行时,Thing<Int>Thing<String>是完全不同的类型。是的,接口支持泛型协变和逆变,但是a)它仅适用于接口(您的代码暗示了一个类),并且b)您仍然必须有一个基础类型。您不能有一个“通配符”。 - Adam Robinson
显示剩余9条评论
3个回答

4
如果您想使用多个不同的模板参数返回一个模板类的实例,一种方法是使用抽象基类(或接口):
abstract class UntypedThing { }
class Thing<T> : UntypedThing
{
    public Thing(T t) { }
}

class Foo
{
    public static UntypedThing createThing(bool flag)
    {
        if (flag)
            return new Thing<int>(5);
        else return new Thing<String>("hello");
    }
}

UntypedThing 类应该包含尽可能不依赖于模板类型的代码。而 Thing 类理想情况下只包含依赖于模板类型的代码。工厂类 Foo 总是返回前者。


1
要明确一点,我认为答案是C#不允许这种事情(即使Java允许),因为C#在协变泛型方面存在限制,而且缺乏C#泛型通配符;但Reinderien的答案是最合理的解决方法。干杯! - Garret Wilson
当你想要你的接口/抽象类拥有一个以参数化类型为方法签名的方法,比如public T GetTypedFoo(),会发生什么呢?无论是抽象类还是实际接口的公共接口都需要知道这个类型。 - theringostarrs
由于在这种情况下,特定类型的信息旨在被委托给子类,因此希望在抽象基类中有一些“GetTypedFoo”,似乎是一种反设计。但是,如果您真的想要,可以使用存储在基类中的反射信息实现。 - Reinderien

4

理论上,您可以使用反射来构建正确的泛型类型,但对于您来说,它将是相当无用的,因为在某个时候,您需要将其向上转换为更不具体的类型。

public class ThingFactory  {
    public object Create(bool flag) {
        Type outputType = null;
        if(flag) {
            outputType = typeof(string);
        } else {
            outputType = typeof(int);
        }
        return Activator.CreateInstance(typeof(Thing<>).MakeGenericType(outputType));
    }
}

如您所见,这样做的价值几乎为零,因为您需要将返回类型转换为所需类型,这意味着确定它的逻辑需要存在于Create方法之外。

我会使用Reinderien的方法并拥有一个非泛型基类。这是最明智和惯用的方法。


0

哦,当我试图做一些简单的事情时,我遇到了麻烦。

原来C# 4允许这种协变——有点像。首先,我必须将Thing变成一个接口,并指定“out”泛型参数:

public interface Thing<out T> {...}

但是如果我做了某些事情,C# 就不让我使用协变性。例如,如果我尝试从接口返回 T:
public interface Thing<out T>
{
  public T GetT();

即使我成功地使用Thing得到了协方差,那我该怎么处理它呢?

Thing<object> thing=createThing();

编译器告诉我无法从使用中推断出类型。
假设我放弃整个 T 的概念,让工厂方法返回类型为 object 的 Thing:
public static Thing<object> createThing() {...}

好的,但现在我把它放在哪里?
IList<Thing<object>> list=new List<Thing<object>>();
Thing<object> thing=createThing();
list.Add(thing);

是的,我必须说这是一个类型为Object的Thing列表,因为C#没有通配符类型。

如果这是Java,我会简单地说:

public class Thing<T> {...}

public static <T> Thing<T> createThing() {...}

List<?> things=new ArrayList<Thing<?>>();
Thing<?> thing=createThing();
things.add(thing);

如果我想要通过指定 T 的特殊类型来获得额外的安全性,我会这样说:
public static <T extends MyBaseType> Thing<T> createThing() {...}

List<? extends MyBaseType> things=new ArrayList<Thing<? extends MyBaseType>>();
Thing<? extends MyBaseType> thing=createThing();
things.add(thing);

然后当我有更多信息时,我会弄清楚T是什么。

所有这些似乎都归结为C#中不完整的通用协变性以及缺乏C#通用通配符。(我仍然认为这不是一个擦除问题。)

那么我该怎么办?唯一简单的事情似乎是按照Reinderien的答案拆分出一个非通用基类。

(我想知道在这个非通用基类中是否可以有object getValue(),然后在子类中使用协变返回T getValue()?啊,我厌倦了这个问题——我将把它留到另一天。)


1
你所寻找的不是协变性。只有通过类型擦除才能实现指定通配符的能力。在.NET运行时中,没有任何泛型参数未指定为“某物”的泛型类型根本不存在。即使具有协变性和逆变性,您也必须指定编译器可以用来与指定类型进行比较以确定兼容性的类型。问题不在于它是不完整的实现,而在于你所要求的根本不是协变性/逆变性。 - Adam Robinson
@Adam,我在这里试图理解。我可以有一个带有返回类型为接口Thing<object>的方法。在这个方法中,我可以返回Thing<int>或者Thing<string>。如果我理解正确的话,这在C# 4中是可行的。那么,这与Java中在签名中使用Thing<?>(它是Thing<? extends object>的简写)有什么不同呢?这里的通用类型不是"object"吗? - Garret Wilson
1
你的具体示例是不正确的(方差仅适用于引用类型,而不适用于像“int”这样的值类型),但一般情况是正确的。是的,你可以使用Thing<object>作为任何引用类型的安全容器,假设T被适当标记。但是,你不能安全地向下转换为更具体的Thing - Adam Robinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接