为什么在C#中使用泛型约束

时间:2010-11-01 23:08:09

标签: c# generics constraints

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

我脑子里浮现的问题是 - 我为什么要使用通用约束?

例如,如果我使用这样的代码:

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

我无法使用T切换此类中ISomething的所有引用?

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

7 个答案:

答案 0 :(得分:47)

你问,“我不能用T切换此课程中ISomething的所有引用吗?”所以我认为你的意思是比较:

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类型。

答案 1 :(得分:8)

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

答案 2 :(得分:4)

以下是仅使用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;

节省了很多麻烦,当然它比这更进一步,但我认为你可以从中得到这个想法。

答案 3 :(得分:2)

首先,您可以在泛型类的泛型方法/方法的代码中调用ISomething中定义的方法。如果允许T为任何类型,那么这是不可能的(尽管你总是可以做一些运行时转换)。

因此,它允许您对T可以强制执行编译时约束,因此在编写代码时依赖于这些约束 - 将运行时错误转换为编译时错误。

答案 4 :(得分:1)

是的,您可以使用ISomething代替T,但这将手动 关闭 泛型类型到普通类。它不再是通用类型。通过使用T,您可以将 打开 类型保留为所需数量的ISomething子类型。 代码重用而不影响类型安全性是这里的关键优势。例如,如果您使用堆栈的ISomethings,您可以将任何ISomething推送到堆栈,但是必须在向实际子类型转发时出现pop ISomething让它变得有用。向下转换会产生一个潜在的失败点,这在通用Stack<T>中不存在,其中T:ISomething

答案 5 :(得分:0)

您班级的消费者可以获得提高类型安全性的好处。

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;
    }
}

答案 6 :(得分: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());

您可以看到我无法将 BMW 添加到 Porsche 列表中,但如果我只是从基类中编程,则可以。