简体   繁体   English

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

[英]Constructing/making a generic type and turning a type constraint into a struct-as-base-type constraint

Normally we cannot constrain a type parameter T to deriving from a sealed type (such as a struct type). 通常,我们不能将类型参数T约束为从密封类型(例如struct类型)派生。 This would be meaningless because there is only one type which could fit, and as such there is no need for generics. 这将毫无意义,因为只有一种类型可以适合,因此不需要泛型。 So constraints like: 所以约束如下:

where T : string

or: 要么:

where T : DateTime

are illegal, for a very good reason. 这是非法的,这是有充分理由的。

However, when constraining to another type parameter, this can sometimes happen when that other type parameter is "substituted" into an actual type (which happens to be sealed). 但是,当约束到另一个类型参数时,有时会在其他类型参数被“替换”为实际类型(恰好被密封)时发生。 Consider the class: 考虑班级:

abstract class ExampleBase<TFromType>
{
  internal abstract void M<TFromMethod>(TFromMethod value) where TFromMethod : TFromType;
}

which is quite innocent. 这是无辜的。 In the concretization: 在具体化中:

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

we make TFromType equal to string . 我们使TFromType等于string This could be meaningful wrt. 这可能是有意义的。 other members than M<> . 其他成员比M<> But M<> itself can still be used: The code: 但是M<>本身仍然可以使用:代码:

  var e1 = new ExampleOne();
  e1.M("abcdefghijklmnopqrstuvwxyz");

will run and write: 将运行并写:

False
kl

to the console. 到控制台。 So the constraint essentially became where TFromMethod : string , but things were still fine. 所以约束基本上变成where TFromMethod : string ,但事情仍然很好。

This question is about what happens if TFromType is a value type. 这个问题是关于TFromType是值类型会发生什么。 So this time we do: 所以这次我们这样做:

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

So why are the calls from the c and d declarations not allowed? 那么为什么不允许来自cd声明的调用呢? After all strangeDate has compile-time type TFromMethod which is constrained to be a DateTime . 毕竟strangeDate有编译时类型TFromMethod ,它被约束为DateTime So surely strangeDate is implicitly a DateTime ? 所以, strangeDateDateTime是隐含的DateTime吗? After all, this worked with string ( class ExampleOne above). 毕竟,这适用于string (上面的class ExampleOne )。

I would prefer an answer which referred to the relevant place in the official C# Language Specification. 我希望得到一个答案,它提到了官方C#语言规范中的相关位置。

Note that when trying to add d , typing strangeDate.Ad ... makes IntelliSense (Visual Studio's autocompleter) come up with a list of all accessible instance members of DateTime , so clearly IntelliSense thinks the call in d should be legal! 请注意,在尝试添加d ,键入strangeDate.Ad ...使IntelliSense(Visual Studio的自动完成程序)提供了DateTime的所有可访问实例成员的列表,因此很明显IntelliSense认为d的调用应该是合法的!

Of course, after c and d have been commented out, we can use ExampleTwo (with e and f ), and the code: 当然,在cd被注释掉后,我们可以使用ExampleTwo (带ef ),代码如下:

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

runs and writes out: 运行并写出:

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

To quote the C# 5.0 specification: 引用C#5.0规范:

6.1.10 Implicit conversions involving type parameters 6.1.10涉及类型参数的隐式转换

The following implicit conversions exist for a given type parameter T : 对于给定的类型参数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 . T到其有效基类C ,从T到任何基类的C ,并从T到由实现的任何接口C [...] [...]

  • [...] [...]

10.1.5 Type parameter constraints 10.1.5类型参数约束

The effective base class of a type parameter T is defined as follows: 类型参数T的有效基类定义如下:

  • [...] [...]
  • 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. 如果T没有类型约束但有一个或多个类型参数约束,则其有效基类是其类型参数约束的有效基类集中包含程度最大的类型(第6.4.2节)。 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 . 出于这些规则的目的,如果T具有作为值类型的约束V ,则使用作为类型的V的最特定基本类型 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 . 这些规则确保有效基类始终是类类型

In other words, given a where U : T constraint with T = string , the effective base class of U is string . 换句话说,给定where U : T T = string where U : T约束,其中T = stringU的有效基类是string Given a where U : T constraint with T = DateTime , the effective base class of U is not DateTime , but ValueType . 给定where U : T T = DateTime where U : T约束,其中T = DateTimeU的有效基类不是DateTime ,而是ValueType And the only relevant implicit conversion of a type parameter is from the type parameter type to its effective base class. 并且类型参数的唯一相关隐式转换是从类型参数类型到其有效基类。

This does seem to lead to some rather odd behaviour, as you've found, but it must nonetheless have been a conscious decision, as it has been explicitly spelled out to behave the way you've seen. 这似乎会导致一些相当奇怪的行为,正如你所发现的那样,但它必须是一个有意识的决定,因为它已被明确地说明你的行为方式。

I would guess that making this work caused difficulties in the compiler, that there are cases where the compiler assumes that it's dealing with a reference type in such cases, and that there is only minimal benefit in making it work. 我猜想,这项工作会给编译器带来困难,有些情况下编译器会假定它在这种情况下处理引用类型,并且使其工作的好处很小。 That's just that, though: a guess. 但就是这样:一个猜测。

In C#5 chapter 13.4.3 Implementation of generic methods , it's said : 在C#5第13.4.3 Implementation of generic methods13.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
    }
}

They say : 他们说 :

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

I understand this as : "you can write it, declare it, even if you can't use it" 我理解为:“你可以写它,声明它,即使你不能使用它”

Here String is a reference type, when we write s = t , we assign the reference, it's valid to assign, because the where T: C constraint allow it. 这里String是一个引用类型,当我们写s = t ,我们分配引用,它对于赋值是有效的,因为where T: C约束允许它。

In the case of DateTime, when we write s = t , we copy the value, not the reference. 在DateTime的情况下,当我们写s = t ,我们复制值,而不是引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM