为什么这个通用约束编译时似乎有一个循环引用
我在csharp中为一个MVCContrib Html helper编写了一个扩展方法,并且对泛型约束的形式感到惊讶,表面上似乎通过type参数循环引用自身。
I have written an extension method in csharp for an MVCContrib Html helper and was surprised at the form of the generic constraint, which on the face of it seems to circularly reference itself through the type parameter.
这说明该方法可以根据需要进行编译和工作。
This being said the method compiles and works as desired.
我希望有人解释为什么工作原理,如果一个更直观的直观语法存在,如果没有人知道为什么?
I would love to have someone explain why this works and if a more intuitive intuitive syntax exists and if not if anyone know why?
这里是编译和函数代码,混淆了问题。 以及使用List< T>的类似方法。
Here is the compiling and function code but I have removed the List of T example as it clouded the issue. as well as an analogous method using a List<T>.
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>似乎所有的意图和目的,通报。然而,如果我注释掉它,我得到一个编译器错误:
In the first method the constraint T : TextInput<T> seems to all intents and purposes, circular. However if I comment it out I get a compiler error:
类型'T'不能用作类型参数'T'方法'MvcContrib.FluentHtml.Elements.TextInput< T>'。
没有从'T'到'MvcContrib.FluentHtml.Elements.TextInput< T>'的盒装转换或类型参数转换
,并且在列表< T& case的错误是:
and in the List<T> case the error(s) are:
最好的重载方法匹配'System.Collections.Generic.List.Add(T)'有一些无效arguments
参数1:不能从'< null>'转换为'T'
可以想象更直观的定义是包括2种类型的定义,对通用类型的引用和对约束类型的引用,例如:
I could imagine a more intuitive definition would be one that includes 2 types, a reference to the Generic Type and a reference to the Constraining Type eg:
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>
但这两个都不编译。
更新:这个问题是我的 2011年2月3日的博客文章。感谢您提出的问题!
UPDATE: This question was the basis of my blog article on the 3rd of February 2011. Thanks for the great question!
这是合法的,不是循环的,而且很常见。我个人不喜欢它。
This is legal, it is not circular, and it is fairly common. I personally dislike it.
我不喜欢的原因是:
1)正如你已经发现的,聪明的代码对于不熟悉类型系统的复杂性的人来说很难直观地理解。
1) It is excessively clever; as you've discovered, clever code is hard for people unfamiliar with the intricacies of the type system to intuitively understand.
2)它不能很好地映射到我的直觉的通用类型表示。我喜欢类来表示事物的类别,以及泛型类来表示参数化类别。对我来说清楚的是,字符串列表和数字列表是两种类型的列表,仅在列表中的事物的类型不同。对我来说很清楚什么是T的TextInput,其中T是T的TextInput。不要让我想。
2) It does not map well to my intuition of what a generic type "represents". I like classes to represent categories of things, and generic classes to represent parameterized categories. It is clear to me that a "list of strings" and a "list of numbers" are both kinds of lists, differing only in the type of the thing in the list. It is much less clear to me what "a TextInput of T where T is a TextInput of T" is. Don't make me think.
3)这种模式经常用于在类型系统中实施一个实际上不能在C#中执行的约束。即这个:
3) This pattern is frequently used in an attempt to enforce a constraint in the type system which is actually not enforcable in C#. Namely this one:
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) { ... }
}
这里的想法是动物的猫只能与其他猫的朋友。
The idea here is "a subclass Cat of Animal can only make friends with other Cats."
问题是所需的规则实际上并未实施:
The problem is that the desired rule is not actually enforced:
class Tiger: Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
现在老虎可以和猫交朋友,但不能和老虎交朋友。
Now a Tiger can make friends with Cats, but not with Tigers.
要在C#中工作,您需要执行以下操作:
To actually make this work in C# you'd need to do something like:
abstract class Animal
{
public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}
其中THISTYPE是一个神奇的新语言功能,这里需要填写自己的类型。
where "THISTYPE" is a magical new language feature that means "an overriding class is required to fill in its own type here".
class Cat : Animal
{
public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}
class Tiger: Animal
{
// illegal!
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
$ b b
不幸的是,这不是类型安全:
Unfortunately, that's not typesafe either:
Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});
如果规则是动物可以与任何种类的朋友交友,那么动物可以朋友与动物。但猫只能与猫,而不是老虎的朋友!参数位置中的东西必须是有效的逆变;在这个假设的情况下,我们需要协方差,这是不可行的。
If the rule is "an animal can make friends with any of its kind" then an animal can make friends with animals. But a cat can only make friends with cats, not tigers! The stuff in the parameter positions has got to be valid contravariantly; in this hypothetical case we'd be requiring covariance, which isn't going to work.
我似乎有点离题。回到这个奇怪的重复模式的主题:我试图只使用这种模式为常见的,容易理解的情况,如由其他答案提到的情况:
I seem to have digressed somewhat. Returning to the subject of this curiously recurring pattern: I try to only use this pattern for common, easily understood situations like the ones mentioned by other answers:
class SortedList<T> where T : IComparable<T>
也就是说,如果我们有任何希望,它们的排序列表。
That is, we need every T to be comparable to every other T if we have any hope of making a sorted list of them.
要被标记为循环,必须有一个真正的循环依赖性:
To actually be flagged as circular there has to be a bona-fide circularity in dependencies:
class C<T, U> where T : U where U : T
一个有趣的类型理论领域处理不好)是非圆形但是无限的通用类型的区域。我写了一个无限型检测器,但它没有使它进入C#4编译器,并不是可能的假设未来版本的编译器的高优先级。如果你对一些非绝对类型的例子感兴趣,或者C#周期检测器弄错的例子,请参阅我的文章:
An interesting area of type theory (that at present the C# compiler handles poorly) is the area of non-circular but infinitary generic types. I have written an infinitary type detector but it did not make it into the C# 4 compiler and is not a high priority for possible hypothetical future versions of the compiler. If you're interested in some examples of infinitary types, or some examples of where the C# cycle detector messes up, see my article on that: