我在 csharp 中为 MVCContrib Html 帮助程序编写了一个扩展方法,并对通用约束的形式感到惊讶,从表面上看,它似乎通过类型参数循环引用自身。
话虽如此,该方法可以按预期编译和工作。
我很乐意有人解释为什么这样做,如果存在更直观的直觉语法,如果不存在,是否有人知道为什么?
这是编译和功能代码,但我删除了 T 列表示例,因为它掩盖了问题。 以及使用 List
namespace MvcContrib.FluentHtml
{
public static class FluentHtmlElementExtensions
{
public static TextInput<T> ReadOnly<T>(this TextInput<T> element, bool value)
where T: TextInput<T>
{
if (value)
element.Attr("readonly", "readonly");
else
((IElement)element).RemoveAttr("readonly");
return element;
}
}
}
<罢工>罢工>
<罢工> /*analogous method for comparison*/
public static List<T> AddNullItem<T>(this List<T> list, bool value)
where T : List<T>
{
list.Add(null);
return list;
}
罢工><罢工>罢工>
在第一种方法中,约束 T : TextInput
“类型‘T’不能用作泛型类型或方法‘MvcContrib.FluentHtml.Elements.TextInput
<罢工>
在 List
“'System.Collections.Generic.List.Add(T)' 的最佳重载方法匹配有一些无效参数
参数 1:无法从“
我可以想象一个更直观的定义是包含 2 种类型,一个是对通用类型的引用,一个是对约束类型的引用,例如:
public static TextInput<T> ReadOnly<T,U>(this TextInput<T> element, bool value)
where U: TextInput<T>
或
public static U ReadOnly<T,U>(this U element, bool value)
where U: TextInput<T>
但是这些都不编译。
最佳答案
更新:这个问题是我 blog article on the 3rd of February 2011 的基础.感谢您提出很好的问题!
这是合法的,不是循环的,而且相当普遍。我个人不喜欢它。
我不喜欢它的原因是:
太聪明了;正如您所发现的,对于不熟悉类型系统复杂性的人来说,聪明的代码很难凭直觉理解。
它与我对泛型“代表”的直觉不符。我喜欢类来表示事物的类别,而通用类来表示参数化的类别。我很清楚“字符串列表”和“数字列表”都是两种列表,只是列表中事物的类型不同。我不太清楚什么是“T 的 TextInput,其中 T 是 T 的 TextInput”。不要让我思考。
此模式经常用于尝试在类型系统中强制执行实际上在 C# 中无法强制执行的约束。即这个:
abstract class Animal<T> where T : Animal<T> { public abstract void MakeFriends(IEnumerable<T> newFriends); } class Cat : Animal<Cat> { public override void MakeFriends(IEnumerable<Cat> newFriends) { ... } }
这里的想法是“Animal 的子类 Cat 只能与其他 Cats 交 friend 。”
问题是所需的规则实际上并没有得到执行:
class Tiger: Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
现在老虎可以和猫交 friend ,但不能和老虎交 friend 。
要在 C# 中实际执行此操作,您需要执行以下操作:
abstract class Animal
{
public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}
其中“THISTYPE”是一个神奇的新语言特性,意思是“需要一个重写类在这里填写它自己的类型”。
class Cat : Animal
{
public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}
class Tiger: Animal
{
// illegal!
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
不幸的是,这也不是类型安全的:
Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});
如果规则是“动物可以与任何同类交 friend ”,那么动物可以与动物交 friend 。但是猫只能和猫交 friend ,不能和老虎交 friend !参数位置中的内容必须反变有效;在这个假设的情况下,我们需要协方差,这是行不通的。
我好像有点跑题了。回到这个奇怪的重复模式的主题:我尝试只将这种模式用于常见的、容易理解的情况,比如其他答案提到的情况:
class SortedList<T> where T : IComparable<T>
也就是说,如果我们希望对它们进行排序,我们需要每个 T 都可以与其他每个 T 进行比较。
要真正被标记为循环,依赖项中必须有一个真正的循环:
class C<T, U> where T : U where U : T
类型理论的一个有趣领域(目前 C# 编译器处理不佳)是非循环但无限 泛型类型的领域。我已经编写了一个 infinitary 类型检测器,但它没有进入 C# 4 编译器,并且对于 future 可能的编译器版本来说,它不是一个高优先级。如果您对无限类型的某些示例或 C# 循环检测器出现问题的某些示例感兴趣,请参阅我的相关文章:
https://ericlippert.com/2008/05/07/covariance-and-contravariance-part-11-to-infinity-but-not-beyond/
关于c# - 为什么这个通用约束在似乎有循环引用时编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3783321/