【发布时间】:2010-11-01 23:08:08
【问题描述】:
我在 MSDN 上阅读了一篇关于 C# 泛型的优秀文章。
我脑海中突然出现的问题是——我为什么要使用通用约束?
例如,如果我使用这样的代码:
public class MyClass<T> where T : ISomething
{
}
我不能用ISomething切换这个类中T的所有引用吗?
使用这种方法有什么好处?
【问题讨论】:
标签: c# generics constraints
我在 MSDN 上阅读了一篇关于 C# 泛型的优秀文章。
我脑海中突然出现的问题是——我为什么要使用通用约束?
例如,如果我使用这样的代码:
public class MyClass<T> where T : ISomething
{
}
我不能用ISomething切换这个类中T的所有引用吗?
使用这种方法有什么好处?
【问题讨论】:
标签: c# generics constraints
你问,“我不能在这个类中用ISomething 切换所有T 的引用吗?”所以我认为你的意思是比较:
public class MyClass<T> where T : ISomething
{
public T MyProperty { get; set; }
}
与:
public class MyClass
{
public ISomething MyProperty { get; set; }
}
在第二个例子中,MyProperty 只保证是ISomething 的一个实例。在第一个示例中,MyProperty 是 T 的任何内容,即使它是 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 类型。
【讨论】:
类型安全。例如,假设您正在创建一个容器。您可以将某些内容传递给该容器并以适当的形式检索它,而无需稍后通过参数化容器进行任何强制转换。您只是定义了您愿意存储在容器中的事物类型的约束。
【讨论】:
这里是一个不同的例子,只使用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;
省去了很多麻烦,当然它比这更进一步,但我认为你可以从中得到想法。
【讨论】:
首先,您可以在代码中为泛型方法/泛型类上的方法调用 ISomething 中定义的方法。如果允许 T 为任何类型,那么这是不可能的(尽管您总是可以进行一些运行时强制转换)。
因此,它允许您对 T 可以是什么实施编译时约束,因此在您编写代码时依赖这些约束 - 将运行时错误转化为编译时错误。
【讨论】:
是的,您可以使用 ISomething 代替 T ,但这会手动关闭普通类的泛型类型。它不再是泛型类型。通过使用 T,您可以将类型 open 保持为您想要的任意数量的 ISomething 子类型。 在不影响类型安全的情况下重用代码是这里的主要优势。例如,如果您使用 ISomething 堆栈,您可以将任何 ISomething 压入堆栈,但必须发生弹出并向下转换为实际子类型的 ISomething 以使其有用。向下转换会创建一个潜在的故障点,它不会出现在通用 Stack<T> 中,其中 T:ISomething
【讨论】:
您的班级的消费者会受益于增加的类型安全性等。
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;
}
}
【讨论】:
这个 youtube 视频实际上展示了通用约束 https://www.youtube.com/watch?v=GlqBRIgMgho 的重要性。
下面是一个很长的文字答案。
“泛型有助于将逻辑与数据类型分离。这样我们 附加任何具有任何逻辑的数据类型以实现高可重用性。”
但很多时候,某些逻辑只能附加到特定的数据类型。
public class CompareNumeric<UNNKOWDATATYPE>
{
public bool Compareme(UNNKOWDATATYPE v1, UNNKOWDATATYPE v2)
{
if (v1 > v2)
{return true;}
else
{return false;}
}
}
例如上面是一个简单的泛型类,如果一个数字大于另一个数字,它会进行比较。现在,大于和小于比较非常特定于数字数据类型。这种比较不能在字符串等非数字类型上进行。
因此,如果有些人使用带有“int”类型的类,它完全有效。
CompareNumeric<int> obj = new CompareNumeric<int>();
bool boolgreater = obj.Compare(10,20);
如果有人使用“双”数据类型再次完全有效。
CompareNumeric<double> obj = new CompareNumeric<double>();
bool boolgreater = obj.Compare(100.23,20.45);
但是在这种逻辑下使用字符串数据类型会导致不良结果。所以我们想限制或限制什么样的类型可以附加到泛型类上,这是通过使用“泛型约束”来实现的。
CompareNumeric<string> obj = new CompareNumeric<string>();
bool boolgreater = obj.Compare(“interview”,”interviewer”);
可以通过在泛型类之后使用“WHERE”关键字指定数据类型来限制泛型类型,如下面的代码所示。现在,如果任何客户端尝试将“字符串”数据类型附加到下面的类中,它将不允许,从而避免不良结果。
public class CompareNumeric<UNNKOWDATATYPE> where UNNKOWDATATYPE : int, double
{
}
【讨论】:
int 或double 作为泛型类型参数。您在上一个示例中使用的。看到这个stackoverflow.com/questions/11770882/…
也许这个简单的例子可能会有所帮助。
如果我有这些课程:
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 列表中,但如果我只是在基类之外进行编程,它将被允许。
【讨论】: