2010-08-03 97 views
51

介绍用户定义的转换运算符

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

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

在我目前的设计中,非托管代码的包装,我使用一个指针,存储在一个实体类。 所有使用指针的类都来自该实体类,例如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 
... 

然而,在运行时,我会得到一个异常说这个转换是不可能的。

结论

所以我在这里,需要从一个用户定义的转换一个基类和C#它拒绝了我。使用方法A,编译器会投诉,但代码在运行时会逻辑运行。使用方法B,编译器不会抱怨,但代码在运行时显然会失败。

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

我知道我可以使用:

解决方案A

class Body : Entity 
{ 
    Body(IntPtr pointer) : base(pointer) { } 

    static Body FromEntity(Entity e) 
    { 
     return new Body(e.Pointer); 
    } 
} 

溶液B

class Body : Entity 
{ 
    Body(IntPtr pointer) : base(pointer) { } 

    Body(Entity e) : base(e.Pointer) { } 
} 

C液

class Entity 
{ 
    IntPtr Pointer; 

    Entity(IntPtr pointer) 
    { 
     this.Pointer = pointer; 
    } 

    Body ToBody() 
    { 
     return new Body(this.Pointer); 
    } 
} 

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

+2

是否有人认为演员操作员做了太多事情?向上转换,向下转换,装箱/取消装箱,用户定义的转换... C++在多年前将其转换操作员分开;也许现在是C#效仿的时候了? – 2010-08-14 10:52:59

回答

33

这不是一个设计缺陷。这里的原因:

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; 

哟!国际海事组织这样疯狂。不要忘记编译器根据编译时间,仅基于所涉及的表达式的编译时间类型来决定。

就我个人而言,我会解决方案C - 甚至可能使它成为一个虚拟的方法。这样Body可能覆盖它只是返回this,如果你想它是身份保持尽可能但在必要时创建一个新的对象。

+1

你不明白我的意图 - 我不想投身于实体,这将是可怕的。 我想要身体的实体。问题是我无法知道它是哪个类,因为我只是包装一个非托管指针。 – Lazlo 2010-08-14 20:15:33

+0

@Lazlo:对不起,我*明白你的意图 - 我只是搞砸了代码示例。 (变量类型是正确的,这只是错误的模式,现在我已经修正了这些错误)。然而,我的帖子背后的推理依然是一样的。你想要一个自定义的转换,其中已经有一个明确的内置转换。表达这种愿望的最清晰的方式是用一种方法。 – 2010-08-14 20:19:21

+0

(构造函数调用也是可以接受的,但在某些情况下会导致流畅的代码。) – 2010-08-14 20:26:59

9

您应该使用您的解决方案B(构造函数参数);首先,这里的原因利用对方提出的解决方案:

  • 解决方案A仅仅是溶液B的包装;
  • 解决方案C是绝对错误的(一个基类,为什么应该知道如何将自己转换为任何子?)

而且,如果Body类都包含附加属性,又该这些被初始化,当你执行你的演员?按照OO语言的约定,使用构造函数并初始化子类的属性要好得多。

+0

+1为最后一个参数。我会考虑的。但没有办法把它当做一个合适的演员? – Lazlo 2010-08-04 19:27:48

+4

我知道如何将自己转换为特定的子类,特别是虚拟的基类没有问题。 Witness Object.ToString和扩展方法IEnumerable.ToList,IEnumerable.ToArray。 – 2010-08-14 20:20:48

15

那么,当你是铸造EntityBody,你是不是真的铸造彼此,而是铸造IntPtr到一个新的实体。

为什么不从IntPtr创建一个明确的转换运算符?

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; 
    } 
} 
+0

到目前为止的最佳选择是,如果赏金没有产生任何其他有价值的结果,将会选择它。 – Lazlo 2010-08-10 04:31:47

2

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

+0

在我目前的包装环境中,演员应该是可能的。我知道,在其他任何情况下,如果不包装定义为指针的对象,则不推荐向下转换。 – Lazlo 2010-08-10 22:51:01

+0

我还是不能想到为什么你不会把这些课程结合起来,如果他们真的是一样的。但是,如果你坚持,我会说最好的选择是有一个明确的函数GetEntityFromBody()或类似的,将返回给定一个Body对象的实体对象。这表明你正在做一些不寻常的事情,同时仍然让你这样做。铸造不应该被滥用。 – siride 2010-08-11 00:26:37

1

如何:

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

代替。

这只是一个化妆变化,我知道,但它很清楚,你可以很容易地改变内部。它也用于包装模式,我不记得名称(用于略微差异的目的)。
你也很清楚你是从一个提供的实体创建一个新的实体,所以不应该让操作符/转换混淆。

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

1

(调用巫术协议...)

这是我用例:

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可以转换为ParseResult<T> - 或者如果不是这个错误。修复是从一个子类型返回和转换:

class ParseResult 
{ 
    public static ErrorParseResult Error(string message); 
    ... 
} 

class ErrorParseResult : ParseResult {} 

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

而且一切都很开心!

0

看来参考平等是不是你的关心,那么你可以说:

  • 代码

    public class Entity { 
        public sealed class To<U> where U : Entity { 
         public static implicit operator To<U>(Entity entity) { 
          return new To<U> { m_handle=entity.Pointer }; 
         } 
    
         public static implicit operator U(To<U> x) { 
          return (U)Activator.CreateInstance(typeof(U), x.m_handle); 
         } 
    
         To() { // not exposed 
         } 
    
         IntPtr m_handle; // not exposed 
        } 
    
        IntPtr Pointer; // not exposed 
    
        public Entity(IntPtr pointer) { 
         this.Pointer=pointer; 
        } 
    } 
    

    public class Body:Entity { 
        public Body(IntPtr pointer) : base(pointer) { 
        } 
    } 
    
    // added for the extra demonstration 
    public class Context:Body { 
        public Context(IntPtr pointer) : base(pointer) { 
        } 
    } 
    

  • 测试

    public static class TestClass { 
        public static void TestMethod() { 
         Entity entity = new Entity((IntPtr)0x1234); 
         Body body = (Entity.To<Body>)entity; 
         Context context = (Body.To<Context>)body; 
        } 
    } 
    

不是你写的访问者,但我把封装进去,不暴露自己的指针。在这个实现的引擎下使用的是一个中间类,它不在继承链中,而是链转换

Activator这里涉及的好处是不增加额外的new()约束因为U已经被约束到Entity并且有一个参数化的构造函数。 To<U>虽然暴露但封闭而不公开其构造函数,它只能从转换运算符实例化。

在测试代码中,实体实际上转换为通用To<U>对象,然后是目标类型,因此是从bodycontext的额外演示。由于To<U>是嵌套类,因此它可以访问包含类的私有Pointer,因此我们可以在不暴露指针的情况下完成任务。

好吧,就是这样。