构造/创建泛型类型并将类型约束转换为struct-as-base-type约束

时间:2015-02-13 12:56:52

标签: c# generics boxing type-constraints generic-constraints

通常我们不能将类型参数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");

将运行并写入:

False
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);
  }
}

那么为什么不允许来自cd声明的来电?毕竟strangeDate有编译时类型TFromMethod被约束为DateTime。所以strangeDate肯定是隐含的DateTime?毕竟,这适用于stringclass ExampleOne以上)。

我更希望得到一个答案,该答案涉及官方C#语言规范中的相关位置。

请注意,在尝试添加d时,键入strangeDate.Ad ...会使IntelliSense(Visual Studio的自动填充程序)提供{{1}的所有可访问实例成员的列表很明显,IntelliSense认为DateTime中的呼叫应该是合法的!

当然,在dc被注释掉之后,我们可以使用dExampleTwoe)和代码:

f

运行并写出:

Friday, 13 February 2015
Was 'strangeDate' a box? False

2 个答案:

答案 0 :(得分:2)

引用C#5.0规范:

  

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

     

对于给定的类型参数T,存在以下隐式转换:

     
      
  • T到其有效基类C,从TC的任何基类,从T到任何界面由C实施。 [...]

  •   
  • [...]

  •   
     

10.1.5类型参数约束

     

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

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

出于这些规则的目的,如果T的约束V值类型,请使用最具体的基本类型{{1}这是一个类类型。这在一个显式给定的约束中永远不会发生,但是当泛型方法的约束由覆盖方法声明或接口方法的显式实现隐式继承时,可能会发生这种情况。

     

这些规则确保有效基类始终是类类型

换句话说,如果V约束where U : TT = string的有效基类为U。如果string约束为where U : T,则T = DateTime的有效基类不是U,而是DateTime。并且类型参数的唯一相关隐式转换是从类型参数类型到其有效基类。

这似乎导致了一些相当奇怪的行为,正如你所发现的那样,但它必须是一个有意识的决定,因为它已经明确地说明了你的行为方式。

我猜想,这项工作会给编译器带来困难,有些情况下编译器会假定它在这种情况下处理引用类型,并且使其工作的好处很小。但就是这样:猜测。

答案 1 :(得分: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的约束,即使此约束在源代码中不可表达。

我理解为:&#34;即使你不能使用它,你也可以写下来,宣布它,#34;

这里String是一种引用类型,当我们写s = t时,我们分配引用,它有效分配,因为where T: C约束允许它。

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