永久性地将派生类转换为基础

时间:2011-02-18 22:45:05

标签: c# .net type-conversion

Class A { }
Class B : A { }

B ItemB = new B();
A ItemA = (A)B;

Console.WriteLine(ItemA.GetType().FullName);

是否可以执行类似上面的操作并让编译器打印出类型A而不是类型B.基本上,是否可以永久地转换对象以使其“丢失”所有派生数据?

6 个答案:

答案 0 :(得分:10)

你要求的是不可能的,原因有两个:

  1. ItemA.GetType()不返回变量ItemA的编译时类型 - 它返回引用的运行时类型 ItemA
  2. 你无法让(A)B导致表示变化转换(即新的A对象),因为用户定义的转换运算符(这里你唯一的希望)无法从派生转换为基数 - 班。您将获得正常,安全的参考转换。
  3. 除此之外,你要求的是非常奇怪的;人们会认为你真的很难违反Liskov的替代原则。这里几乎肯定存在一个严重的设计缺陷,你应该解决。

    如果你还想这样做;您可以编写一种方法,通过新建A然后复制数据,从B手动构建A。这可能以ToA()的形式存在  B上的实例方法。

    如果您将此问题描述为“我如何从现有的 A ?构​​建A”,则更有意义:在A上创建一个复制构造函数,其中声明看起来像public A(A a){...},它与子类特定的细节无关。这为您提供了从A或其子类之一的现有实例创建A的一般方法。

答案 1 :(得分:10)

我最近遇到了将旧项目迁移到Entity Framework的问题。如上所述,如果您有来自实体的派生类型,则无法存储它,只能存储基类型。 解决方案是带反射的扩展方法。

    public static T ForceType<T>(this object o)
    {
        T res;
        res = Activator.CreateInstance<T>();

        Type x = o.GetType();
        Type y = res.GetType();

        foreach (var destinationProp in y.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
            var sourceProp = x.GetProperty(destinationProp.Name);
            if (sourceProp != null)
            {
                destinationProp.SetValue(res, sourceProp.GetValue(o));
            }
        }

        return res;
    }

它不是太整洁,所以如果你真的没有其他选择,请使用它。

答案 2 :(得分:6)

为了娱乐,如果你想丢失所有的派生数据,你可以这样做:

   class Program
    {
        [DataContract(Name = "A", Namespace = "http://www.ademo.com")]
        public class A { }
         [DataContract(Name = "A", Namespace = "http://www.ademo.com")]
        public class B : A  {
             [DataMember()]
             public string FirstName;
        }  

    static void Main(string[] args)
    {
        B itemB = new B();
        itemB.FirstName = "Fred";
        A itemA = (A)itemB; 
        Console.WriteLine(itemA.GetType().FullName);
        A wipedA = WipeAllTracesOfB(itemB);
        Console.WriteLine(wipedA.GetType().FullName);
    }

    public static A WipeAllTracesOfB(A a)
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(A));

        using (MemoryStream ms = new MemoryStream())
        {
            serializer.WriteObject(ms, a);
            ms.Position = 0;

            A wiped = (A)serializer.ReadObject(ms);

            return wiped;
        }
    }
}

如果您使用调试器,您会看到FirstName在转换为A时仍存储在FirstName字段中,当您从WipeAllTracesOfB获得A时,没有FirstName或任何B的跟踪。

答案 3 :(得分:2)

(这可能很明显,但是......)

re:accepted answer by @Ani

  

如果你还想这样做;你可以编写一个方法,通过新建A然后复制数据,从B手动构造A。这可能作为B上的ToA()实例方法存在。

或使用Automapper复制数据:

Mapper.CreateMap<ChildClass, BaseClass>();
// ...later...
var wipedInstance = Mapper.Map<BaseClass>(instanceOfChildClass);

然后wipedInstance.GetType()将为typeof(BaseClass)

答案 4 :(得分:1)

不,您不能强制子类型的实例报告它的类型名称是基本类型。

请注意,当您使用指向B类实例的ItemA变量时,您只能使用ItemA变量访问A类中定义的字段和方法。很少有地方可以实际看到ItemA指向A类以外的实例的事实 - 在子类中重写的虚拟方法是一种情况,并且运行时类型本身的操作,例如GetType()。

如果你问这个问题是因为当你想要一个A类实例时,当你发送一个B类实例时某些代码失败了,听起来你应该仔细看看那个代码到看看它做错了什么。如果它正在测试GetType.LastName,那么它就会崩溃并且脑死亡。如果它正在测试x IS A那么传递B的实例将会通过,一切都应该没问题。

答案 5 :(得分:0)

转换不会更改对象的类型。所有的转换方法都是告诉编译器将对象视为什么类型(假设对象实际上是该类型)。

A ItemA = (A)ItemB;正在说ItemB,将其视为A类型,并将其称为ItemA

当你致电ItemA.GetType()时,你要求的是真实的类型,而不仅仅是它被视为的类型,真正的类型是B

(我将你的代码示例更改为我认为你的意思,因为你的代码不会编译)