C# 来自基类的用户定义的转换运算符
导言 我知道“不允许向基类或从基类进行用户定义的转换”。MSDN对此规则进行了解释,“您不需要此运算符。” 我知道不需要用户定义的基类转换,因为这显然是隐式的。但是,我确实需要从基类进行转换 在我当前的设计(非托管代码的包装器)中,我使用存储在实体类中的指针。 使用指针的所有类都派生自该实体类,例如Body类 因此,我有: 方法AC# 来自基类的用户定义的转换运算符,c#,casting,C#,Casting,导言 我知道“不允许向基类或从基类进行用户定义的转换”。MSDN对此规则进行了解释,“您不需要此运算符。” 我知道不需要用户定义的基类转换,因为这显然是隐式的。但是,我确实需要从基类进行转换 在我当前的设计(非托管代码的包装器)中,我使用存储在实体类中的指针。 使用指针的所有类都派生自该实体类,例如Body类 因此,我有: 方法A class Entity { IntPtr Pointer; Entity(IntPtr pointer) { this.P
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);
}
}
class Body : Entity
{
Body(IntPtr pointer) : base(pointer) { }
static Body FromEntity(Entity e)
{
return new Body(e.Pointer);
}
}
这个演员是非法的。(请注意,我没有费心编写访问器)。
如果没有它,编译器将允许我执行以下操作:
方法B
(Body)myEntity
...
class Body : Entity
{
Body(IntPtr pointer) : base(pointer) { }
Body(Entity e) : base(e.Pointer) { }
}
然而,在运行时,我会得到一个异常,说这个强制转换是不可能的
结论
因此,我现在需要从基类进行用户定义的转换,而C#拒绝了我。使用方法A,编译器会抱怨,但代码在运行时会在逻辑上工作。使用方法B,编译器不会抱怨,但代码在运行时显然会失败
在这种情况下,我觉得奇怪的是MSDN告诉我我不需要这个操作符,编译器的行为就好像它是隐式的(方法B)。我该怎么办
我知道我可以使用:
解决方案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);
}
}
class Body : Entity
{
Body(IntPtr pointer) : base(pointer) { }
static Body FromEntity(Entity e)
{
return new Body(e.Pointer);
}
}
解决方案B
(Body)myEntity
...
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#不信任我使用他们的cast系统编写自己的基到子转换一样。您应该使用解决方案B(构造函数参数);首先,以下是为什么不使用其他建议的解决方案:
- 溶液A只是溶液B的包装物李>
- 解决方案C是错误的(为什么基类应该知道如何将自己转换成任何子类?)
另外,如果
Body
类包含其他属性,那么在执行强制转换时,这些属性应该初始化为什么?按照OO语言中的惯例,使用构造函数并初始化子类的属性要好得多。好吧,当您将实体
转换为主体
时,您并不是真正地将一个实体转换为另一个实体,而是将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;
}
}
你不能这样做的原因是因为在一般情况下它是不安全的。考虑可能性。如果您希望能够做到这一点,因为基类和派生类是可互换的,那么您实际上只有一个类,您应该合并这两个类。如果您想让您的CAST运算符方便地将基降序派生,那么您必须考虑并非每个类型作为基类类型的变量都指向指向要将其转换为特定派生类的实例。可能是这样,但您必须先检查,否则可能会出现无效的强制转换异常。这就是为什么人们通常不赞成向下投射,而这只不过是在拖拽中向下投射而已。我建议你重新考虑你的设计。这不是设计缺陷。原因如下:
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
,如果您希望它在可能的情况下保持身份,但在必要的情况下创建一个新对象。怎么样:
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());
}
一切都很快乐 似乎参考平等不是你关心的问题,那么你可以说:
- 代码
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 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; } }
公共静态类TestClass{ 公共静态void TestMethod(){ 实体实体=新实体((IntPtr)0x1234); Body Body=(Entity.To)Entity; 上下文=(Body.To)Body; } }
Activator
有助于不添加额外的new()
约束,因为U
已约束到实体,并且具有参数化构造函数<代码>到
虽然已公开,但在不公开其构造函数的情况下已密封,但它只能从转换运算符实例化
在测试代码中,实体实际上转换为一个通用的对象,然后转换为目标类型,额外的演示也是如此
Dictionary<string, Parent>
Class1 v1 = ...
Class2 v2 = ...
v1 = v2;
v1 = v2;
int v1;
decimal v2;
. . .
v1 = (int)v2;
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;
}
}
}
}
}
}