为什么在C#中使用通用约束?

31

我在MSDN上阅读了一篇关于C#泛型的优秀文章。

我脑海中浮现出的问题是:为什么我应该使用泛型约束?

例如,如果我使用以下代码:

public class MyClass<T> where T : ISomething
{
}

我能否在该类中将所有T的引用都替换为ISomething

采用这种方法有什么好处?


http://msdn.microsoft.com/en-us/library/d5x73970.aspx - zebrabox
7个回答

50
你问,“我不能用ISomething替换该类中所有的T吗?” 我认为你的意思是比较以下内容:
public class MyClass<T> where T : ISomething 
{ 
    public T MyProperty { get; set; }
}

使用:

public class MyClass 
{
    public ISomething MyProperty { get; set; }
}

在第二个例子中,MyProperty只能保证是ISomething的一个实例。而在第一个例子中,MyPropertyT的任何类型,即使它是ISomething的具体子类型。考虑一个ISomething的具体实现:
public class MySomething : ISomething
{
    public string MyOtherProperty { get; set; }
}

现在,如果我们使用第一个通用示例,我们可以有以下内容:
MyClass<MySomething> myClass = new MyClass<MySomething>();
Console.WriteLine(myClass.MyProperty.MyOtherProperty);

另一方面,如果我们使用第二个示例,由于MyOtherProperty仅被认为是一个ISomething,因此我们无法访问它:

MyClass myClass = new MyClass();
Console.WriteLine(myClass.MyProperty.MyOtherProperty); // Won't compile, no property "MyOtherProperty"

另外,这些类型约束的有用之处在于您可以引用 MyProperty(类型为 T)并访问 ISomething 的成员。换句话说,如果声明了 ISomething 如下:

public interface ISomething 
{
    public string SomeProperty { get; set; }
}

那么你可以访问 MyProperty.SomeProperty。 如果省略了where T : ISomething,则无法访问SomeProperty,因为T只能被认为是object类型。


8

类型安全。例如,假设你正在创建一个容器。你可以通过将该容器参数化来传递一些内容,并以正确的形式检索它,而不必进行任何强制转换。你只是在定义存储在容器中的事物类型的约束。


5
这是一个示例,仅使用 List<> 的区别如下:
图像列表不会是通用的,而只会在使用泛型时到处使用 IListElement。现在想象一下,您有一个类似于这样的对象。
class Element : IListElement
{
   public string Something { get; set; }
}

现在我只需要执行 list.Add(element); 就可以了,这样与真正的 List<Element> 没有区别。但是当我检索数据时情况就不同了,如果我使用使用 IListElement 的列表,那么我必须将我的数据强制转换回来,以便我可以从中获取 Something。因此,我需要执行以下操作:

string s = ((Element)list[0]).Something;

对于通用的情况,我只需要执行以下操作:

string s = list[0].Something;

它可以避免很多麻烦,当然它的功能不止于此,但我认为你可以从这里得到一些想法。


2

首先,你可以在通用方法/通用类的代码中调用ISomething中定义的方法。如果T允许是任何类型,则不可能实现此操作(尽管您始终可以进行一些运行时转换)。

这使您能够对T的可选类型施加编译时约束,并因此在编写代码时依靠这些约束-将运行时错误转换为编译时错误。


1

你的类的消费者将受益于增加的类型安全性等方面。

class Widget : IPokable { }

// No generics
Widget w = (Widget)list[0]; // cast can fail

// With generics
Widget w = list[0];

如果没有泛型,如果列表包含IPokable对象,则仍然需要强制转换

您正在实现的类受益于在泛型对象上使用特定方法。

class PokableList<T> where T : IPokable {
    public T PokeAndGet() {
        currentObj.Poke();
        return currentObj;
    }
}

1

是的,您可以使用ISomething代替T,但这将手动将泛型类型关闭为普通类。它将不再是泛型类型。通过使用T,您可以将类型保持开放到尽可能多的ISomething子类型。在这里,代码重用而不会影响类型安全性是关键优势。例如,如果您使用一个ISomethings的堆栈,您可以将任何ISomething推入堆栈,但必须使用向下转换到ISomething的实际子类型才能进行弹出以使其有用。向下转换创建了潜在的故障点,在泛型Stack<T>中不存在,其中T:ISomething。


0
也许这个简单的例子可以帮助你。
如果我有这些类:
public class ListOfCars<T> : List<T> where T : Car { }

public abstract class Car { }
public class Porsche : Car { }
public class Bmw : Car { }

...然后如果我写下这段代码:

var porsches = new ListOfCars<Porsche>();

// OK
porsches.Add(new Porsche());

//Error - Can't add BMW's to Porsche List
porsches.Add(new Bmw());

你可以看到,我不能将宝马添加到保时捷列表中,但如果我仅仅按照基类进行编程,则是允许的。

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