Permanently cast derived class to base

17,709

Solution 1

What you ask for is impossible for two reasons:

  1. ItemA.GetType() does not return the compile-time type of the variable ItemA - it returns the run-time type of the object referred to by ItemA.
  2. There's no way you could make (A)B result in a representation-changing conversion (i.e. a new A object) because user-defined conversion operators (your only hope here) cannot convert from derived to base-classes. You're just going to get a normal, safe, reference-conversion.

That aside, what you ask for is very strange; one would think you're trying really hard to violate Liskov's substiution principle. There's almost certainly a serious design-flaw here that you should address.

If you still want to do this; you could write a method that manually constructs an A from a B by newing up an A and then copying data over. This might exist as a ToA() instance-method on B.

If you characterized this problem as "How do I construct an A from an existing A?", it makes a lot more sense: create a copy-constructor on A, whose declaration looks like public A(A a){...}, which is agnostic to subclass-specific details. This gives you a general means to create an A from an existing instance of A or one of its subclasses.

Solution 2

I've recently run into this migrating an old project to Entity Framework. As it was mentioned, if you have a derived type from an entity, you can't store it, only the base type. The solution was an extension method with reflection.

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

It's not too neat, so use this if you really have no other option.

Solution 3

For amusement, if you wanted to lose all of the derived data you could do this:

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

If you use the debugger you will see the FirstName is still stored in the field FirstName when it is cast to an A, when you get an A back from WipeAllTracesOfB there is no FirstName, or any trace of B.

Solution 4

(this is probably obvious, but...)

re: the accepted answer by @Ani:

If you still want to do this; you could write a method that manually constructs an A from a B by newing up an A and then copying data over. This might exist as a ToA() instance-method on B.

Or use Automapper to copy the data back:

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

then wipedInstance.GetType() would be typeof(BaseClass)

Solution 5

No, you can't force an instance of a child type to report that it's type name is the base type.

Note that when you are using the ItemA variable pointing to the instance of class B, you can only access the fields and methods defined in class A using the ItemA variable. There are very few places where the fact that ItemA points to an instance of something other than class A can actually be observed - virtual methods overridden in the child classes is one case, and operations on the runtime type itself, such as GetType().

If you're asking this question because some piece of code is failing when you send it an instance of class B when it is expecting an instance of class A, it sounds like you should be taking a closer look at that code to see what it's doing wrong. If it's testing GetType.LastName, then it's broken and braindead. If it's testing x IS A then passing an instance of B will pass and everything should be fine.

Share:
17,709

Related videos on Youtube

Telavian
Author by

Telavian

CTO of a small startup working to redefine how the billions of available consumer reviews are packaged for consumption.

Updated on February 14, 2020

Comments

  • Telavian
    Telavian almost 4 years
    Class A { }
    Class B : A { }
    
    B ItemB = new B();
    A ItemA = (A)B;
    
    Console.WriteLine(ItemA.GetType().FullName);
    

    Is it possible to do something like above and have the compiler print out type A instead of type B. Basically, is it possible to permanently cast an object so it "loses" all the derived data?

    • Telavian
      Telavian almost 13 years
      Yes at that point ItemA is a B. What I am really trying to get around is a WCF problem of requiring exact types. I know I could use KnownTypes, but I would rather not if possible.
    • Telavian
      Telavian almost 13 years
      Also from a object oriented perspective there is nothing wrong with the sample above. Permanently going the opposite direction should be a problem, but not problem going this direction.
    • denver
      denver over 10 years
      This is an old question, but I wanted to point out the key is the problem with WCF requiring exact types. This seems to be a common problem I have not seen a solution for. Basically you are working with derived types on the server, but the client is expecting a base type, and WCF fails because it does not know what to do with the derived type. The client should not need to know about the derived type, only the base type, so you don't want to force extra types on the client that are only relevant to the server.
    • J.T. Taylor
      J.T. Taylor about 10 years
      This can also be a problem if you want to just serialize the base type, and not the whole derived type. For example, to save work, or there are members on the derived type that do not serialize.
  • Telavian
    Telavian almost 13 years
    Very nice. I like your method name.
  • Telavian
    Telavian almost 13 years
    I am not really violating any principles because I just asking the compiler to forget that that item has any derived information. It is like forgetting that an object is an Employee and treating them as a Person. My main goal is to get around WCF's problem with derived types.
  • RichardOD
    RichardOD almost 13 years
    Thanks, it was fun writing it!
  • Abhijeet
    Abhijeet almost 11 years
    +1 for the workaround, The trick here was to assign A & B same name in the DataContractSerializer. Works great in console application. But shall not be able to use in MVC-App with WCF. :-(
  • Yuck
    Yuck almost 10 years
    @Telavian You're not alone. This is a problem for EF, too. If you have a type that derives from a type mapped in EF and try to update that object, EF will complain that it doesn't know how to map it because the derived type is not mapped, yet the base type is.
  • Asad Saeeduddin
    Asad Saeeduddin over 9 years
    I'm not sure you understand the Liskov Substitution Principle. In all places where you would need an A, you can use an A, a B, or an A that has been extracted from a B (which is what the OP is talking about). None of those scenarios constitute a violation of LSP.
  • supertopi
    supertopi almost 9 years
    AutoMapper is a nice way to solve derived class serialization problems with WCF
  • Michal Dobrodenka
    Michal Dobrodenka about 8 years
    Very nice solution when there is no other way. Useful for special serializers etc.
  • iGanja
    iGanja over 7 years
    I see this violation argument a lot, however @Yuck brings up the most common reason I can think of doing this. We often extend entity classes for dto purposes, and receive the same dto back to be acted on. EF wants to act on the base class; it knows nothing of the derived class. Does this violate some academic OOP concept? Perhaps. Does it get the job done? Absolutely. Will anyone care a year from now? Nope.
  • Daren
    Daren almost 7 years
    Works great here. Had to amend the line if (sourceProp != null) as follows to check that the destination property was writeable: if (sourceProp != null && destinationProp.CanWrite)

Related