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

时间:2010-08-03 21:56:51

标签: c# casting

简介

我知道“不允许在基类之间进行用户定义的转换”。作为对此规则的解释,MSDN给出了“您不需要此运算符。”

我知道不需要用户定义的转换基类,因为这显然是隐式完成的。但是,我确实需要从基类转换

在我目前的设计中,非托管代码的包装器,我使用指针存储在Entity类中。 所有使用指针的类都派生自该Entity类,例如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#不信任我足以使用他们的演员系统编写我自己的基础到子编译。

11 个答案:

答案 0 :(得分:39)

这不是设计缺陷。原因如下:

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;

育!那种疯狂在于,IMO。不要忘记编译器仅在编译时的情况下决定这一点,仅基于所涉及表达式的编译时类型。

就个人而言,我会选择解决方案C - 甚至可能使它成为一种虚拟方法。这样Body 可以覆盖它以返回this,如果您希望它在可能的情况下保留 ,但在必要时创建新对象

答案 1 :(得分:18)

好吧,当你将Entity投射到Body时,你并非真的将一个投射到另一个,而是将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;
    }
}

答案 2 :(得分:9)

你应该使用你的解决方案B(构造函数参数);首先,这就是为什么使用其他提议的解决方案:

  • 解决方案A仅是解决方案B的包装器;
  • 解决方案C是错误的(为什么基类应该知道如何将自身转换为任何子类?)

此外,如果Body类要包含其他属性,那么在执行转换时应将这些属性初始化为什么?使用构造函数并初始化子类的属性要好得多,就像OO语言中的约定一样。

答案 3 :(得分:2)

你不能这样做的原因是因为它在一般情况下不安全。考虑可能性。如果你想能够这样做,因为基类和派生类是可以互换的,那么你真的只有一个类,你应该合并这两个类。如果你想拥有你的强制转换操作符以便能够向下转换为派生,那么你必须考虑到并非每个输入类型的变量都会指向你试图转换它的特定派生类的实例至。 可能就是这样,但你必须先检查,否则冒一个无效的强制转换异常的风险。这就是为什么向下倾斜通常不受欢迎的原因,这只不过是拖累的倾向。我建议你重新考虑你的设计。

答案 4 :(得分:2)

怎么样:

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

代替。

这只是一个美容改变,我知道,但很清楚,你可以轻松改变内部。它也用在一个我不记得名字的包装图案中(为了略微差异的目的) 很明显,你是从一个提供的实体创建一个新的实体,所以不应该像操作员/转换那样混淆。

注意:没有使用编译器,因此存在拼写错误的可能性。

答案 5 :(得分: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可以推断,但它可以返回可转换的无类型ParseResultParseResult<T> - 或者如果没有这个错误的话。修复方法是从子类型返回并转换:

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

class ErrorParseResult : ParseResult {}

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

一切都很开心!

答案 6 :(得分: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,因此我们可以在不暴露指针的情况下完成任务。

嗯,就是这样。

答案 7 :(得分:0)

你可以使用泛型, 它可能像打击

public class a<based>
    {
        public static implicit operator b(a<based> v)
        {
            return new b();
        }
    }

    public class b
        : a<b>
    {
    }

答案 8 :(得分:0)

老实说,我认为最初的要求被误解了。

考虑一个简单的情况,即基类只是作为相关类的分组。

例如:

class Parent ...
class Child1 : Parent ...
class Child2 : Parent ...

程序员知道如何从一个子类显式转换为另一个子类。

Parent类可以在例如:

中使用
Dictionary<string, Parent>

我认为最初的要求是:

如何进行编码:

Class1 v1 = ...
Class2 v2 = ...

v1 = v2;

Parent中,有明确的代码可以完成从Class2Class1对象的转换。

我的代码中有这种情况。

我要做的最好的事情就是向Parent类添加一个属性,该属性知道如何进行转换并返回正确的类型化对象。

这迫使我编写代码:

v1 = v2.AsClass1;

AsClass1中的属性Parent知道如何进行从Class2Class1的实际转换。

老实说,这是一个代码冲突(丑陋;有损于简单性,可以使表达式荒谬,冗长,晦涩难懂,并且最令人讨厌的是它缺乏优雅感),但是我能想到的最好的方法。

是的,是的,您猜对了,Parent类还包含AsClass2方法:-)

我想做的只是:

v1 = v2;

并使编译器静默调用我指定的方法进行转换。

我真的不明白为什么编译器会支持此:-(

在我看来,这真的没有什么不同:

int v1;
decimal v2;
. . .
v1 = (int)v2;

编译器知道静默调用某些内置转换方法。

答案 9 :(得分:0)

尽管这是一个古老的讨论,但我只是想补充一些自己的经验。

在类库中,我们曾经有一个数学对象,例如2D点和2D向量。因为两个类的对象的特征基本相同(尽管并不完全相同,所以都需要两个类),所以我们的想法是定义一个Vector2D并从中派生Point2D。这样可以省去很多重复的定义,但是无法实现从向量到点的自定义转换运算符。

因此,由于我们强烈希望在代码中故意交换类型,因此我们决定放弃派生的想法,独立声明两个类并引入隐式转换运算符。然后我们就可以自由交换代码中的两种类型。

答案 10 :(得分:0)

哎呀,我最终只是在修改后的实体中执行了一个简单的Cast()方法。也许我只是漏了一点,但是我需要修改基本类型,以便将代码保留在新对象中以执行x。如果编译器允许我使用公共静态显式运算符。继承将其弄乱了显式强制转换运算符。

用法:

Action = null

示例:

Combination