C#泛型中是否有一种合理的方法来处理“默认”类型参数?

107
在C++模板中,可以指定某个类型参数为默认值。也就是说,如果没有明确指定,它将使用类型T。
在C#中是否可以实现类似的功能或近似的功能?
我正在寻找类似以下内容的东西:
public class MyTemplate<T1, T2=string> {}

因此,该类型的实例未明确指定 T2

MyTemplate<int> t = new MyTemplate<int>();

希望的是:

MyTemplate<int, string> t = new MyTemplate<int, string>();

最终,我考虑的情况是有一个相当广泛使用的模板,但我正在考虑扩展一个额外的类型参数。我可以子类化,不过我很好奇是否还有其他选项。


我认为我回答的工厂方法模式更适合你的情况。唯一的缺点是你需要通过静态工厂方法实例化对象,而不是直接使用构造函数。但这种解决方案让你只有一个类,而不是两个。 - Thanasis Ioannidis
6个回答

91

子类化是最好的选择。

我建议你将主要通用类进行子类化:

class BaseGeneric<T,U>

使用一个特定的类进行子类化:

class MyGeneric<T> : BaseGeneric<T, string>

这样可以让你的逻辑保持在一个地方(基类),同时也容易提供两种使用选项。根据类的不同,可能需要很少的额外工作来实现这一点。


1
啊...这很有道理。如果类型参数提供了唯一的签名,那么类型名称可以相同吗? - el2iot2
4
@ee:是的,泛型可以通过参数数量进行“重载”。 - Mehrdad Afshari
@ee:是的,但我会谨慎地这样做。在.NET中这是“合法”的,但可能会导致混淆。我宁愿让派生字符串类型的名称与主泛型类相似(这样很明显/易于找到),但名称应该明确表明它是一个字符串。 - Reed Copsey
我可以两种方式看待它。如果你期望过载版本被平等使用,我认为我会使用相同的类型名称。如果底层基础很少使用,仅用于特殊情况,那么选择一个能够表现出这一点的名称是有意义的。感谢所有提供高质量答案的人。 - el2iot2
5
例如,Predicate<T> 只是 Func<T,bool> 的重命名,因为它用于不同的目的。 - Reed Copsey
显示剩余3条评论

24

一种解决方案是子类化。另外一个我会使用的解决方案是工厂方法(与var关键字结合使用)。

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

在上面的例子中,val2 的类型是 MyTemplate<int,string>,而不是从它派生的类型。
类型 class MyStringTemplate<T>:MyTemplate<T,string> 不同于 MyTemplate<T,string>。这在某些情况下可能会造成问题。例如,您无法将 MyTemplate<T,string> 的实例转换为 MyStringTemplate<T>

3
这是最实用的方法。非常好的解决方案。 - T-moty

22

你也可以这样创建一个名为Overload的类

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}

在发布答案之前,请先阅读其他答案,因为你的解决方案可能与其他人相同。 - Cheng Chen
13
已被接受的答案是创建一个具有不同名称的类,我的解决方案是在相同的类中进行重载。 - Nerdroid
2
不,两者都创建了一个新类。这里名称并不重要。MyTemplate<T1>是与MyTemplate<T1, T2>AnotherTemplate<T1>都不同的类。 - Cheng Chen
具体来说,你示例中的类型名称是 MyTemplate\1MyTemplate`2`。正如Danny Chen所说,它们是不同的类。 - JamesQMurphy
11
即使真正的类型有所不同,保持相同的名称会使编码更容易,这是清晰和正确的。并非每个人都明白类“overload”可以以这种方式使用。@DannyChen是正确的 - 从技术角度来看,结果是相同的,但这个答案更接近于实现OP要求的内容。 - Kuba
3
不幸的是,这个解决方案仍然不如真正的“默认”泛型参数,因为MyTemplate<T1, string>不能赋值给MyTemplate<T1>,这可能是可取的。 - Felk

12

C#不支持这样的特性。

正如你所说,如果它没有被密封(sealed),你可以对其进行子类化,并复制所有构造函数声明,但这是完全不同的事情。


3

很遗憾,C#不支持你想要做的事情。这将是一个难以实现的功能,因为参数的默认类型必须遵循泛型约束,并且在CLR尝试确保类型安全时可能会引起麻烦。


1
不完全是这样。它本可以使用属性(就像 VB.NET 中的默认参数)来实现,并让编译器在编译时替换它。主要原因是 C# 的设计目标。 - Mehrdad Afshari
编译器必须确保默认参数满足通用约束。此外,默认参数本身将成为通用约束,因为在方法中对类型参数做出的任何假设都要求任何非默认类型参数从中继承。 - Andrew Hare
@Andrew,默认参数不需要是一个泛型约束。如果这个行为更像C++中的默认模板参数,那么在扩展automatonic的类之后,我们可以完全正常地进行以下操作:MyTemplate x = null因为T2没有泛型约束,因此float是可以使用的,尽管默认类型为string。因此,默认模板参数本质上只是为了将MyTemplate<int>写作MyTemplate<int, string>的简写而已。 - Tyler Laing
@Andrew,我同意C#编译器应该验证这样的默认模板参数是否满足现有的泛型约束。C#编译器已经在类声明中验证了类似的内容,例如在Reed的BaseGeneric<T,U>类中添加一个泛型约束,如“where U:ISomeInterface”,然后MyGeneric<T>将无法通过编译并显示错误。验证默认模板参数将是非常相似的:编译器在类的声明时进行验证,错误消息可以是相同的或非常相似的。 - Tyler Laing
“实现这个功能可能会很困难。”这是无稽之谈。实现起来非常简单。验证类型是否符合模板约束的工作并不会因为候选类型出现在文件中的不同位置(即在模板中而不是在模板实例化处)而变得更加困难。 - Mud

2
如果您想将类型重载到接口中,可以这样做: 示例:
public interface ISRD<TItem, TId>
{
    Task SaveAsync(TItem item);
    Task<TItem> GetAsync(TId id);
    Task DeleteAsync(TItem item);
}

public interface ISRD<TItem>: ISRD<TItem, Guid> { }

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