Noe*_*mer 52 c# generics nullable value-type implicit-conversion
这是一种我无法理解的怪异行为.在我的例子我有一个类Sample<T>和隐式转换操作符T来Sample<T>.
private class Sample<T>
{
public readonly T Value;
public Sample(T value)
{
Value = value;
}
public static implicit operator Sample<T>(T value) => new Sample<T>(value);
}
Run Code Online (Sandbox Code Playgroud)
使用空值类型为出现问题时,T例如int?.
{
int? a = 3;
Sample<int> sampleA = a;
}
Run Code Online (Sandbox Code Playgroud)
这里是关键部分:
在我看来,这不应该编译,因为Sample<int>定义了一个转换,从int以Sample<int>而不是从int?到Sample<int>.但它编译并成功运行!(我的意思是调用转换运算符3并将其分配给该readonly字段.)
它变得更糟.这里不调用转换运算符,sampleB将其设置为null:
{
int? b = null;
Sample<int> sampleB = b;
}
Run Code Online (Sandbox Code Playgroud)
一个很好的答案可能会分为两部分:
are*_*yla 42
您可以看看编译器如何降低此代码:
int? a = 3;
Sample<int> sampleA = a;
Run Code Online (Sandbox Code Playgroud)
进入这个:
int? nullable = 3;
int? nullable2 = nullable;
Sample<int> sample = nullable2.HasValue ? ((Sample<int>)nullable2.GetValueOrDefault()) : null;
Run Code Online (Sandbox Code Playgroud)
因为Sample<int>是一个类,所以可以为其实例分配一个空值,并且使用这样的隐式运算符,也可以分配一个可为空的对象的基础类型.所以这些作业是有效的:
int? a = 3;
int? b = null;
Sample<int> sampleA = a;
Sample<int> sampleB = b;
Run Code Online (Sandbox Code Playgroud)
如果Sample<int>是a struct,那当然会给出错误.
编辑: 那为什么这可能?我无法在规范中找到它,因为它是故意的规范违规,这只是为了向后兼容而保留.你可以在代码中阅读它:
DELIBERATE SPEC VIOLATION:
即使转换的返回类型不是非可空值类型,本机编译器也允许"提升"转换.例如,如果我们有从struct S到string的转换,那么从S开始"转换"转换?to native被本机编译器认为存在,语义为"s.HasValue?(string)s.Value:(string)null".Roslyn编译器为了向后兼容性而使该错误永久化.
这就是在Roslyn中实现这个"错误"的方式:
否则,如果转换的返回类型是可空值类型,引用类型或指针类型P,那么我们将其降低为:
Run Code Online (Sandbox Code Playgroud)temp = operand temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)
因此,根据给定用户定义的转换运算符的规范, T -> U存在提升的运算符T? -> U?,其中T和U不可为空的值类型.然而,U由于上述原因,这种逻辑也被实现用于转换运算符,其中它是参考类型.
第2部分如何防止在这种情况下编译代码?那么有一种方法.您可以专门为可空类型定义其他隐式运算符,并使用属性对其进行装饰Obsolete.这需要将type参数T限制为struct:
public class Sample<T> where T : struct
{
...
[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(T? value) => throw new NotImplementedException();
}
Run Code Online (Sandbox Code Playgroud)
此运算符将被选为可空类型的第一个转换运算符,因为它更具体.
如果不能进行这样的限制,则必须分别为每个值类型定义每个运算符(如果确实可以使用反射并使用模板生成代码):
[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(int? value) => throw new NotImplementedException();
Run Code Online (Sandbox Code Playgroud)
如果在代码中的任何位置引用,那将会出错:
错误CS0619'Sample.implicit operator Sample(int?)'已过时:'某些错误消息'
Evk*_*Evk 19
我认为这是提升转换运营商的行动.规格说:
给定一个用户定义的转换运算符,它从非可空值类型S转换为非可空值类型T,存在一个从S转换的提升转换运算符?到T?.这个提升的转换操作符执行从S解包?到S后跟用户定义的从S到T的转换,接着是从T到T的包装,除了空值S?直接转换为空值T?.
看起来它在这里不适用,因为虽然type S是值类型here(int),但type T不是值类型(Sampleclass).但是,Roslyn存储库中的这个问题表明它实际上是规范中的一个错误.Roslyn 代码文档证实了这一点:
如上所述,这里我们以两种方式偏离规范.首先,如果正常形式不适用,我们只检查提升的表格.其次,我们应该只适用于当转换参数和返回类型起重语义两个非空值类型.
事实上,本机编译器根据以下内容确定是否检查提升的表单:
- 我们最终是从可空值类型转换的类型吗?
- 转换的参数类型是否为非可空值类型?
- 我们最终将类型转换为可空值类型,指针类型或引用类型吗?
如果所有这些问题的答案都是"是",那么我们可以提升为可空,并查看最终的运算符是否适用.
如果编译器遵循规范 - 它会在这种情况下产生一个错误,正如你所期望的那样(在某些旧版本中它会这样做),但现在却没有.
总结一下:我认为编译器使用隐式运算符的提升形式,根据规范应该是不可能的,但编译器与规范不同,因为:
正如描述提升操作符如何工作的第一个引用中所描述的(除了我们允许T作为引用类型) - 您可能会注意到它描述了您的情况下究竟发生了什么.nullvalues S(int?)直接赋给T(Sample)而没有转换运算符,并且非null被解包int并运行在你的运算符中(T?如果T是引用类型,显然不需要换行).
为什么第一个代码段中的代码会编译?
Nullable<T>可以在此处找到源代码中的代码示例:
[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value) {
return value.Value;
}
[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault(T defaultValue) {
return hasValue ? value : defaultValue;
}
Run Code Online (Sandbox Code Playgroud)
该结构Nullable<int>具有一个被覆盖的显式操作者以及方法GetValueOrDefault这两个中的一个用于通过编译器转换int?到T.
之后它运行了implicit operator Sample<T>(T value).
发生的事情的粗略画面是:
Sample<int> sampleA = (Sample<int>)(int)a;
Run Code Online (Sandbox Code Playgroud)
如果我们typeof(T)在Sample<T>隐式运算符内打印它将显示:System.Int32.
在第二个方案中的编译器不使用implicit operator Sample<T>,只是分配null到sampleB.
| 归档时间: |
|
| 查看次数: |
3163 次 |
| 最近记录: |