Constructing/making 泛型类型并将类型约束转换为基类型约束
Constructing/making a generic type and turning a type constraint into a struct-as-base-type constraint
通常我们不能将类型参数 T
限制为从密封类型(例如 struct
类型)派生。这将毫无意义,因为只有一种类型适合,因此不需要泛型。所以约束如下:
where T : string
或:
where T : DateTime
是非法的,这是有充分理由的。
但是,当约束到另一个类型参数时,当另一个类型参数 "substituted" 变成实际类型(碰巧是密封的)时,有时会发生这种情况。考虑 class:
abstract class ExampleBase<TFromType>
{
internal abstract void M<TFromMethod>(TFromMethod value) where TFromMethod : TFromType;
}
这是很无辜的。具体化中:
class ExampleOne : ExampleBase<string>
{
internal override void M<TFromMethod>(TFromMethod strangeString)
{
var a = string.IsNullOrEmpty(strangeString);
Console.WriteLine(a);
var b = strangeString.Substring(10, 2);
Console.WriteLine(b);
}
}
我们让 TFromType
等于 string
。这可能是有意义的。 M<>
以外的其他成员。但是M<>
本身还是可以使用的:代码:
var e1 = new ExampleOne();
e1.M("abcdefghijklmnopqrstuvwxyz");
将运行写成:
错误
kl
到控制台。所以约束基本上变成了where TFromMethod : string
,但事情仍然很好。
这个问题是关于如果 TFromType
是一个值类型会发生什么。所以这次我们这样做:
class ExampleTwo : ExampleBase<DateTime>
{
internal override void M<TFromMethod>(TFromMethod strangeDate)
{
// var c = DateTime.SpecifyKind(strangeDate, DateTimeKind.Utc); // will not compile
// var d = strangeDate.AddDays(66.5); // will not compile
var e = string.Format(CultureInfo.InvariantCulture, "{0:D}", strangeDate); // OK, through boxing
Console.WriteLine(e);
var f = object.ReferenceEquals(strangeDate, strangeDate);
Console.WriteLine("Was 'strangeDate' a box? " + f);
}
}
那么为什么不允许来自 c
和 d
声明的调用? 毕竟 strangeDate
具有编译时类型 TFromMethod
被限制为 DateTime
。所以肯定 strangeDate
隐式地是一个 DateTime
?毕竟,这适用于 string
(上面的 class ExampleOne
)。
我希望参考官方 C# 语言规范中相关位置的答案。
请注意,当尝试添加 d
时,键入 strangeDate.Ad
... 会使 IntelliSense(Visual Studio 的自动完成器)列出 [的所有可访问实例成员=30=],很明显 IntelliSense 认为 d
中的调用应该是合法的!
当然,在c
和d
被注释掉之后,我们可以使用ExampleTwo
(加上e
和f
),而代码:
var e2 = new ExampleTwo();
e2.M(new DateTime(2015, 2, 13));
运行s 并写出:
2015 年 2 月 13 日,星期五
'strangeDate' 是一个盒子吗?假
在 C#5 章节 13.4.3 Implementation of generic methods
中说:
interface I<C>
{
void H<T>(T t) where T: C;
}
class C: I<string>
{
void H<T>(T t)
{
string s = t; // Ok
}
}
他们说:
Note that the assignment from t to s is valid since T inherits a constraint of T: string, even though this constraint is not expressible in source code.
我理解为:"you can write it, declare it, even if you can't use it"
这里String是一个引用类型,当我们写s = t
时,我们给引用赋值,赋值是有效的,因为where T: C
约束允许。
对于 DateTime,当我们写 s = t
时,我们复制的是值,而不是引用。
引用 C# 5.0 规范:
6.1.10 Implicit conversions involving type parameters
The following implicit conversions exist for a given type parameter T
:
From T
to its effective base class C
, from T
to any base class of C
, and from T
to any interface implemented by C
. [...]
[...]
10.1.5 Type parameter constraints
The effective base class of a type parameter T
is defined as follows:
- [...]
- If
T
has no class-type constraint but has one or more type-parameter constraints, its effective base class is the most encompassed type (§6.4.2) in the set of effective base classes of its type-parameter constraints. The consistency rules ensure that such a most encompassed type exists.
- [...]
For the purpose of these rules, if T
has a constraint V
that is a value-type, use instead the most specific base type of V
that is a class-type. This can never happen in an explicitly given constraint, but may occur when the constraints of a generic method are implicitly inherited by an overriding method declaration or an explicit implementation of an interface method.
These rules ensure that the effective base class is always a class-type.
换句话说,给定 where U : T
约束 T = string
,U
的有效基数 class 是 string
。给定 T = DateTime
的 where U : T
约束,U
的有效基数 class 不是 DateTime
,而是 ValueType
。并且类型参数唯一相关的隐式转换是从类型参数类型到它的有效基 class.
正如您所发现的,这似乎确实导致了一些相当奇怪的行为,但它一定是一个有意识的决定,因为它已被明确说明以您所见的方式行事。
我猜想使这项工作在编译器中造成困难,在某些情况下编译器假定它在这种情况下处理引用类型,并且使它工作的好处微乎其微。不过,仅此而已:一个猜测。
通常我们不能将类型参数 T
限制为从密封类型(例如 struct
类型)派生。这将毫无意义,因为只有一种类型适合,因此不需要泛型。所以约束如下:
where T : string
或:
where T : DateTime
是非法的,这是有充分理由的。
但是,当约束到另一个类型参数时,当另一个类型参数 "substituted" 变成实际类型(碰巧是密封的)时,有时会发生这种情况。考虑 class:
abstract class ExampleBase<TFromType>
{
internal abstract void M<TFromMethod>(TFromMethod value) where TFromMethod : TFromType;
}
这是很无辜的。具体化中:
class ExampleOne : ExampleBase<string>
{
internal override void M<TFromMethod>(TFromMethod strangeString)
{
var a = string.IsNullOrEmpty(strangeString);
Console.WriteLine(a);
var b = strangeString.Substring(10, 2);
Console.WriteLine(b);
}
}
我们让 TFromType
等于 string
。这可能是有意义的。 M<>
以外的其他成员。但是M<>
本身还是可以使用的:代码:
var e1 = new ExampleOne();
e1.M("abcdefghijklmnopqrstuvwxyz");
将运行写成:
错误 kl
到控制台。所以约束基本上变成了where TFromMethod : string
,但事情仍然很好。
这个问题是关于如果 TFromType
是一个值类型会发生什么。所以这次我们这样做:
class ExampleTwo : ExampleBase<DateTime>
{
internal override void M<TFromMethod>(TFromMethod strangeDate)
{
// var c = DateTime.SpecifyKind(strangeDate, DateTimeKind.Utc); // will not compile
// var d = strangeDate.AddDays(66.5); // will not compile
var e = string.Format(CultureInfo.InvariantCulture, "{0:D}", strangeDate); // OK, through boxing
Console.WriteLine(e);
var f = object.ReferenceEquals(strangeDate, strangeDate);
Console.WriteLine("Was 'strangeDate' a box? " + f);
}
}
那么为什么不允许来自 c
和 d
声明的调用? 毕竟 strangeDate
具有编译时类型 TFromMethod
被限制为 DateTime
。所以肯定 strangeDate
隐式地是一个 DateTime
?毕竟,这适用于 string
(上面的 class ExampleOne
)。
我希望参考官方 C# 语言规范中相关位置的答案。
请注意,当尝试添加 d
时,键入 strangeDate.Ad
... 会使 IntelliSense(Visual Studio 的自动完成器)列出 [的所有可访问实例成员=30=],很明显 IntelliSense 认为 d
中的调用应该是合法的!
当然,在c
和d
被注释掉之后,我们可以使用ExampleTwo
(加上e
和f
),而代码:
var e2 = new ExampleTwo();
e2.M(new DateTime(2015, 2, 13));
运行s 并写出:
2015 年 2 月 13 日,星期五 'strangeDate' 是一个盒子吗?假
在 C#5 章节 13.4.3 Implementation of generic methods
中说:
interface I<C>
{
void H<T>(T t) where T: C;
}
class C: I<string>
{
void H<T>(T t)
{
string s = t; // Ok
}
}
他们说:
Note that the assignment from t to s is valid since T inherits a constraint of T: string, even though this constraint is not expressible in source code.
我理解为:"you can write it, declare it, even if you can't use it"
这里String是一个引用类型,当我们写s = t
时,我们给引用赋值,赋值是有效的,因为where T: C
约束允许。
对于 DateTime,当我们写 s = t
时,我们复制的是值,而不是引用。
引用 C# 5.0 规范:
6.1.10 Implicit conversions involving type parameters
The following implicit conversions exist for a given type parameter
T
:
From
T
to its effective base classC
, fromT
to any base class ofC
, and fromT
to any interface implemented byC
. [...][...]
10.1.5 Type parameter constraints
The effective base class of a type parameter
T
is defined as follows:
- [...]
- If
T
has no class-type constraint but has one or more type-parameter constraints, its effective base class is the most encompassed type (§6.4.2) in the set of effective base classes of its type-parameter constraints. The consistency rules ensure that such a most encompassed type exists.- [...]
For the purpose of these rules, if
T
has a constraintV
that is a value-type, use instead the most specific base type ofV
that is a class-type. This can never happen in an explicitly given constraint, but may occur when the constraints of a generic method are implicitly inherited by an overriding method declaration or an explicit implementation of an interface method.These rules ensure that the effective base class is always a class-type.
换句话说,给定 where U : T
约束 T = string
,U
的有效基数 class 是 string
。给定 T = DateTime
的 where U : T
约束,U
的有效基数 class 不是 DateTime
,而是 ValueType
。并且类型参数唯一相关的隐式转换是从类型参数类型到它的有效基 class.
正如您所发现的,这似乎确实导致了一些相当奇怪的行为,但它一定是一个有意识的决定,因为它已被明确说明以您所见的方式行事。
我猜想使这项工作在编译器中造成困难,在某些情况下编译器假定它在这种情况下处理引用类型,并且使它工作的好处微乎其微。不过,仅此而已:一个猜测。