用户自定义的基类转换运算符
介绍
我知道“不允许用户自定义转换到基类或从基类转换”。 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一个新的对象。
那么,当你将Entity
成Body
,你并不是真的把一个转换成另一个,而是把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); ... }
一切都很开心!