用户自定义的基类转换运算符

介绍

我知道“不允许用户自定义转换到基类或从基类转换”。 MSDN给出了这个规则的解释,“你不需要这个操作符”。

我明白,用户定义的基类转换是不需要的,因为这显然是隐含的。 但是,我确实需要基类转换。

在我目前的devise中,一个非托pipe代码的包装,我使用一个指针,存储在一个实体类。 所有使用指针的类派生自该实体类,例如Body类。

因此我有:

方法A.

class Entity { IntPtr Pointer; Entity(IntPtr pointer) { this.Pointer = pointer; } } class Body : Entity { Body(IntPtr pointer) : base(pointer) { } explicit operator Body(Entity e) { return new Body(e.Pointer); } } 

这个演员是非法的。 (请注意,我没有打扰访问者)。 没有它,编译器允许我这样做:

方法B

 (Body)myEntity ... 

但是,在运行时,我会得到一个exception说这个投射是不可能的。

结论

因此,在这里,我需要一个基类的用户定义转换,而C#拒绝给我。 使用方法A,编译器会发出抱怨,但代码会在运行时在逻辑上工作。 使用方法B,编译器不会抱怨,但代码在运行时显然会失败。

在这种情况下,我觉得奇怪的是,MSDN告诉我,我不需要这个运算符,编译器的行为就好像是隐式的(方法B)。 我应该做些什么?

我知道我可以使用:

答案A

 class Body : Entity { Body(IntPtr pointer) : base(pointer) { } static Body FromEntity(Entity e) { return new Body(e.Pointer); } } 

解决schemeB

 class Body : Entity { Body(IntPtr pointer) : base(pointer) { } Body(Entity e) : base(e.Pointer) { } } 

解决schemeC

 class Entity { IntPtr Pointer; Entity(IntPtr pointer) { this.Pointer = pointer; } Body ToBody() { return new Body(this.Pointer); } } 

但说实话,所有这些语法都是可怕的,实际上应该是强制性的。 那么,有什么方法可以使演员工作? 这是一个C#devise缺陷还是我错过了一个可能性? 就好像C#不相信我足够用自己的投射系统写我自己的基地到小孩的转换。

这不是一个devise缺陷。 原因如下:

 Entity entity = new Body(); Body body = (Body) entity; 

如果允许您在此处编写自己的用户定义的转换,则会有两个有效的转换:尝试执行正常的转换(这是参考转换,保留标识)和用户定义的转换。

应该用哪个? 你真的想要这样做,这些会做不同的事情吗?

 // Reference conversion: preserves identity Object entity = new Body(); Body body = (Body) entity; // User-defined conversion: creates new instance Entity entity = new Body(); Body body = (Body) entity; 

育! 国际海事组织,这样的疯狂谎言。 不要忘记编译器在编译时根据所涉及的expression式的编译时types来决定它。

就我个人而言,我会解决schemeC – 甚至可能使它成为一个虚拟的方法。 那样的话, Body 可以重载它,直到返回this ,如果你希望它在可能的情况下保持身份但是在必要时创build一个新的对象。

那么,当你将EntityBody ,你并不是真的把一个转换成另一个,而是把IntPtr成一个新的实体。

为什么不从IntPtr创build一个显式的转换运算符?

 public class Entity { public IntPtr Pointer; public Entity(IntPtr pointer) { this.Pointer = pointer; } } public class Body : Entity { Body(IntPtr pointer) : base(pointer) { } public static explicit operator Body(IntPtr ptr) { return new Body(ptr); } public static void Test() { Entity e = new Entity(new IntPtr()); Body body = (Body)e.Pointer; } } 

你应该使用你的解决schemeB(构造函数的参数); 首先,为什么使用其他build议的解决scheme:

  • 解决schemeA仅仅是解决schemeB的包装;
  • 解决schemeC是错的(为什么基类知道如何将自己转换成任何子类?)

另外,如果Body类包含额外的属性,那么在执行演员时应该将这些属性初始化为什么? 按照OO语言的惯例,使用构造函数并初始化子类的属性要好得多。

你做不到的原因是因为在一般情况下是不安全的。 考虑可能性。 如果你想能做到这一点,因为基类和派生类是可以互换的,那么你真的只有一个类,你应该合并这两个。 如果你想让你的转换操作符为了方便能够将基类转换为派生类,那么你必须考虑到,并不是所有types化为基类的variables都会指向你想要转换的特定派生类的实例至。 这可能是这样的,但你必须先检查,否则冒着一个无效的转换exception。 这就是为什么向下倾倒通常是不被接受的,而这只不过是拖拽而已。 我build议你重新考虑你的devise。

怎么样:

 public class Entity {...} public class Body : Entity { public Body(Entity sourceEntity) { this.Pointer = sourceEntity.Pointer; } } 

所以在代码中你不必写:

 Body someBody = new Body(previouslyUnknownEntity.Pointer); 

但你可以使用

 Body someBody = new Body(previouslyUnknownEntity); 

代替。

我知道,这只是一个整体的变化 ,但它很清楚,你可以很容易地改变内部。 它也用于包装模式,我不记得(为了略有差异的目的)的名称。
同样清楚的是,你正在从一个提供的实体创build一个新的实体,所以不应该因为运营商/转换而混淆。

注意:没有使用过编译器,所以可能会有拼写错误。

(调用死灵协议…)

这是我的用例:

 class ParseResult { public static ParseResult Error(string message); public static ParseResult<T> Parsed<T>(T value); public bool IsError { get; } public string ErrorMessage { get; } public IEnumerable<string> WarningMessages { get; } public void AddWarning(string message); } class ParseResult<T> : ParseResult { public static implicit operator ParseResult<T>(ParseResult result); // Fails public T Value { get; } } ... ParseResult<SomeBigLongTypeName> ParseSomeBigLongTypeName() { if (SomethingIsBad) return ParseResult.Error("something is bad"); return ParseResult.Parsed(new SomeBigLongTypeName()); } 

这里Parsed()可以从它的参数中推断出T ,但是Error不能,但是它可以返回一个可以转换成ParseResult<T>的无types的ParseResult – 否则会出现这个错误。 修复是从一个子types返回和转换:

 class ParseResult { public static ErrorParseResult Error(string message); ... } class ErrorParseResult : ParseResult {} class ParseResult<T> { public static implicit operator ParseResult<T>(ErrorParseResult result); ... } 

一切都很开心!