4

通常我们不能将类型参数限制为T从密封类型(例如struct类型)派生。这将毫无意义,因为只有一种类型可以适合,因此不需要泛型。所以约束如下:

where T : string

或者:

where T : DateTime

是非法的,这是有充分理由的。

但是,当约束到另一个类型参数时,有时会在另一个类型参数被“替换”为实际类型(恰好是密封的)时发生这种情况。考虑类:

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");

将运行并写入:

错误的
吉隆坡

到控制台。所以约束基本上变成了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);
  }
}

那么为什么不允许来自cand声明的调用呢?d毕竟strangeDate编译时类型TFromMethod被限制为DateTime. 所以肯定strangeDate是隐含的DateTime?毕竟,这适用于stringclass ExampleOne上面)。

我更喜欢在官方 C# 语言规范中提到相关位置的答案。

请注意,当尝试添加时d,键入strangeDate.Ad... 会使 IntelliSense(Visual Studio 的自动完成器)列出所有可访问的实例成员的列表DateTime,因此很明显 IntelliSense 认为调用d应该是合法的!

当然,在注释掉 and 之后,我们可以使用c( with and ),代码如下:dExampleTwoef

  var e2 = new ExampleTwo();
  e2.M(new DateTime(2015, 2, 13));

运行并写出:

2015 年 2 月 13 日星期五
'strangeDate' 是一个盒子吗?错误的
4

2 回答 2

2

引用 C# 5.0 规范:

6.1.10 涉及类型参数的隐式转换

给定类型参数存在以下隐式转换T

  • T到其有效基类C, 从T到 的任何基类C, 从T到 实现的任何接口C。[...]

  • [...]

10.1.5 类型参数约束

类型参数的有效基类T定义如下:

  • [...]
  • 如果T没有类类型约束但有一个或多个类型参数约束,则其有效基类是其类型参数约束的一组有效基类中包含最多的类型(第 6.4.2 节)。一致性规则确保存在这种最广泛的类型。
  • [...]

出于这些规则的目的,如果有一个value-typeT约束,请改用最具体的基类型class -type。这永远不会发生在显式给定的约束中,但是当泛型方法的约束被重写方法声明或接口方法的显式实现隐式继承时可能会发生。VV

这些规则确保有效的基类始终是class-type

换句话说,给定一个具有 的where U : T约束T = string, 的有效基类Ustring。给定带有 的where U : T约束T = DateTime, 的有效基类U不是DateTime,而是ValueType。类型参数唯一相关的隐式转换是从类型参数类型到其有效基类。

正如您所发现的那样,这似乎确实会导致一些相当奇怪的行为,但它仍然必须是一个有意识的决定,因为它已明确说明要按照您所看到的方式行事。

我猜想使这项工作在编译器中造成困难,在某些情况下编译器假定它在这种情况下处理引用类型,并且使其工作的好处很小。不过,仅此而已:猜测。

于 2015-02-17T15:24:03.623 回答
0

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

他们说 :

请注意,从 t 到 s 的赋值是有效的,因为 T 继承了 T: string 的约束,即使此约束在源代码中不可表达。

我将其理解为:“您可以编写它,声明它,即使您不能使用它”

这里String是一个引用类型,我们写s = t的时候,我们赋值引用,赋值是有效的,因为where T: C约束允许。

在 DateTime 的情况下,当我们编写 时s = t,我们复制的是值,而不是引用。

于 2015-02-13T13:15:46.660 回答