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

简介

我知道“不允许用户定义的与基类的转换”。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 # 不够信任我,不能使用他们的强制转换系统编写我自己的基础到子系统的转换。

30335 次浏览

您应该使用您的解决方案 B (构造函数参数) ; 首先,下面是 没有使用其他建议的解决方案的原因:

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

另外,如果 Body类包含其他属性,那么在执行强制转换时应该将这些属性初始化为什么?按照面向对象语言中的惯例,使用构造函数并初始化子类的属性要好得多。

那么,当您将 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;
}
}

你不能这样做的原因是因为它在一般情况下是不安全的。考虑一下可能性。如果您希望能够这样做,因为基类和派生类是可互换的,那么您实际上只有一个类,您应该合并这两个类。如果您希望使用强制转换运算符来方便地将基类强制转换为派生类,那么您必须考虑到,并不是每个作为基类输入的变量都会指向您试图将其强制转换为的特定派生类的实例。也许吧是这样的,但是您必须首先进行检查,否则可能会出现无效的强制转换异常。这就是为什么人们普遍不赞成下跌,而这只不过是拖累下跌而已。我建议你重新考虑一下你的设计。

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

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;

呀!我觉得,这就是疯狂的谎言。不要忘记,编译器仅根据所涉及的表达式的 编译时类型在 编译时决定这一点。

Personally I'd go with solution C - and possibly even make it a virtual method. That way Body 可以 override it to just return this, if you want it to be identity preserving 有可能吗 but creating a new object where necessary.

这个怎么样:

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

取而代之。

这只是一个 表面上的改变,我知道,但它是相当清楚的,你可以改变内部很容易。它也用在一个包装器模式,我不记得一个名称的(略有差异。用途)。
很明显,您正在从提供的实体创建一个新实体,因此不应该像操作符/转换那样令人困惑。

注意: 没有使用过编译器,所以可能有打字错误。

(调用巫术协议...)

以下是我的用例:

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>的无类型的 ParseResult——或者如果没有这个错误的话就是这样。补丁是从子类型返回和转换:

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


class ErrorParseResult : ParseResult {}


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

一切都很幸福!

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

  • 密码

    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) {
    }
    }
    

and the

  • Test

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

You didn't write the accessors but I took the encapsulation into account, to not expose their pointers. Under the hood of this implementation is use an intermediate class which is not in the inheritance chain but chain the conversion.

Activator involved here is good for not adding extra new() constraint as U are already constrained to Entity and have a parameterized constructor. To<U> though is exposed but sealed without exposing its constructor, it can only be instantiated from the conversion operator.

In the test code, the entity actually converted to a generic To<U> object and then the target type, so is the extra demonstration from body to context. Because To<U> is a nested class, it can access the private Pointer of the containing class, thus we can accomplish things without exposing the pointer.

Well, that's it.

你可以用通用的, 有可能就像可卡因

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


public class b
: a<b>
{
}

说实话,我觉得最初的请求被误解了。

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

例如:

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

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

例如,Parent类可以用于:

Dictionary<string, Parent>

我认为最初的要求是:

如何编写代码:

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


v1 = v2;

Parent中,有明确的代码执行从 Class2Class1对象的转换。

我的代码中就有这种情况。

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

这迫使我写代码:

v1 = v2.AsClass1;

其中,Parent中的属性 AsClass1知道如何实现从 Class2Class1的实际转换。

老实说,这是一个代码组装(它丑陋; 有损于简单性,可以使表达式长得可笑,模糊不清,最令人恼火的是它缺乏优雅) ,但它是我能想到的最好的。

而且,是的,您猜到了,Parent类还包括 AsClass2方法: -)

我只想:

v1 = v2;

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

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

在我看来,这其实没有什么不同:

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

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

这是一个老的讨论,虽然,但我只是想添加一些我自己的经验。

在类库中,我们曾经有过像2D 点和2D 向量这样的数学对象。因为这两个类的对象的特征大致相同(虽然不完全相同,所以需要两个类) ,所以我们的想法是定义一个 Vector2D并从中派生出 Point2D。它可以省去许多重复的定义,但是不可能实现从向量到点的自定义转换运算符。

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

Ugg,我最终只是在修改后的实体中执行了一个简单的 Cast ()方法。也许我只是没有抓住要点,但是我需要修改我的基类型,这样我就可以把我的代码放在新对象中来做 x。如果编译器允许的话,我会使用公共静态显式运算符。继承会扰乱显式转换操作符。

用法:

var newItem = UpgradedEnity(dbItem);
var stuff = newItem.Get();

样本:

public class UpgradedEnity : OriginalEnity_Type
{
public string Get()
{
foreach (var item in this.RecArray)
{
//do something
}
return "return something";
}


public static UpgradedEnity Cast(OriginalEnity_Type v)
{
var rv = new UpgradedEnity();
PropertyCopier<OriginalEnity_Type, UpgradedEnity>.Copy(v, rv);
return rv;
}


public class PropertyCopier<TParent, TChild> where TParent : class
where TChild : class
{
public static void Copy(TParent from, TChild to)
{
var parentProperties = from.GetType().GetProperties();
var childProperties = to.GetType().GetProperties();


foreach (var parentProperty in parentProperties)
{
foreach (var childProperty in childProperties)
{
if (parentProperty.Name == childProperty.Name && parentProperty.PropertyType == childProperty.PropertyType)
{
childProperty.SetValue(to, parentProperty.GetValue(from));
break;
}
}
}
}
}
}