2011-02-18 25 views
20
Class A { } 
Class B : A { } 

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

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

¿Es posible hacer algo como arriba y tener la impresión compilador del tipo de salida A en lugar del tipo B. Básicamente, es posible lanzar un objeto de forma permanente por lo que "pierde" todo el datos derivados?permanentemente fundido clase derivada basar

+0

Agregando a la respuesta sobre por qué no puede hacer esto, no debería tener que hacerlo. Cuando desee saber si un objeto pertenece a un tipo de la clase base, no tiene que verificar la equidad de los tipos de tiempo de ejecución, pero puede usar funciones como 'is' o' IsAssignableFrom'. –

+0

¿Qué produce ItemA.GetType(). FullName? 'Namespace.B'? –

+5

Sí, en ese momento, el ItemA es un B. Lo que realmente estoy tratando de evitar es un problema de WCF que requiere tipos exactos. Sé que podría usar KnownTypes, pero preferiría no hacerlo si es posible. – Telavian

Respuesta

10

lo que pide es imposible por dos razones:

  1. ItemA.GetType() no devuelve el tipo en tiempo de compilación de la variable ItemA - se devuelve el tipo en tiempo de ejecución del objeto refiere por ItemA.
  2. No se puede hacer que (A)B produzca una conversión de cambio de representación (es decir, un nuevo objeto A) porque los operadores de conversión definidos por el usuario (su única esperanza aquí) no pueden convertir de clases derivadas a clases base. Obtendrá una conversión de referencia normal y segura.

Aparte de eso, lo que usted pide es muy extraño; uno pensaría que está tratando realmente de violar el principio de substiución de Liskov. Es casi seguro que hay un problema grave de diseño que debe abordar.

Si aún desea hacer esto; puede escribir un método que construya manualmente un A desde un B al actualizar un A y luego copiar los datos. Esto podría existir como un ToA() instancia-método en B.

Si caracterizado a este problema como "? ¿Cómo construyo un A partir de una existentes Un", que tiene mucho más sentido: crear un constructor de copia en A, cuya declaración se parece a public A(A a){...}, que es agnóstico a detalles específicos de subclase. Esto le proporciona un medio general para crear un A a partir de una instancia existente de A o una de sus subclases.

+14

No estoy realmente violando ningún principio porque simplemente le pido al compilador que olvide que ese elemento tiene información derivada. Es como olvidar que un objeto es un Empleado y tratarlo como una Persona.Mi objetivo principal es resolver el problema de WCF con los tipos derivados. – Telavian

+0

@Downvoter: ¿Algún comentario? – Ani

+3

@Telavian No estás solo. Este es un problema para EF, también. Si tiene un tipo que deriva de un tipo mapeado en EF e intenta actualizar ese objeto, EF se quejará de que no sabe cómo mapear ** porque el tipo derivado no está mapeado **, sin embargo, el tipo de base es . – Yuck

1

No, no puede forzar una instancia de un tipo secundario para informar que su nombre de tipo es el tipo base.

Tenga en cuenta que cuando utiliza la variable ItemA que apunta a la instancia de la clase B, solo puede acceder a los campos y métodos definidos en la clase A utilizando la variable ItemA. Hay muy pocos lugares donde el hecho de que ItemA apunta a una instancia de algo diferente a la clase A se puede observar realmente: los métodos virtuales anulados en las clases secundarias son un caso y las operaciones en el tipo de tiempo de ejecución en sí, como GetType().

Si hace esta pregunta porque un fragmento de código está fallando cuando le envía una instancia de clase B cuando espera una instancia de clase A, parece que debería echar un vistazo más de cerca a ese código para ver lo que está haciendo mal. Si está probando GetType.LastName, está roto y es una muerte cerebral. Si está probando x IS A, pasará una instancia de B y todo estará bien.

6

Para la diversión, si usted quiere perder todos los datos derivados usted puede hacer esto:

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

Si utiliza el depurador verá el FirstName aún se guarda en el FirstName campo cuando está echada a una A, cuando obtienes una A de regreso de WipeAllTracesOfB, no hay ningún nombre, ni ningún rastro de B.

+0

Muy agradable. Me gusta el nombre de tu método. – Telavian

+0

¡Gracias, fue divertido escribirlo! – RichardOD

+0

+1 para la solución temporal, El truco aquí fue asignar 'A' y' B' el mismo nombre en 'DataContractSerializer'. Funciona muy bien en la aplicación de consola. Pero no podrá usar en MVC-App con WCF. :-( – Abhijeet

0

La fundición no cambia el tipo de objeto. Todos los medios de conversión le dicen al compilador de qué tipo tratar el objeto como (suponiendo que el objeto realmente sea de ese tipo).

A ItemA = (A)ItemB; está diciendo tomar ItemB, lo tratan como si fuera de tipo A, y lo llaman ItemA

Cuando se llama a ItemA.GetType() que está pidiendo el tipo real, no sólo el tipo que está siendo tratado como, y el tipo real es B

(I cambió su código de ejemplo a lo que creo que quería decir, ya que lo que tiene no se compilará)

+0

En CI podría lanzar un perro a un mono y al compilador no le importaría. Me preguntaba si había algo así en .Net. – Telavian

2

(esto es probablemente obvio, pero ...)

re: la accepted answer by @Ani:

Si todavía quiere hacer esto; podría escribir un método que construya manualmente una A desde una B al actualizar una A y luego copiar datos. Esto podría existir como un Toa (ejemplo- método) sobre B.

O utilice Automapper para copiar los datos de nuevo:

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

continuación wipedInstance.GetType() habría typeof(BaseClass)

+1

AutoMapper es una buena manera de resolver los problemas de serialización derivados con WCF – supertopi

+0

@ supertopi I <3 AutoMapper para todas las cosas ... – drzaus

8

me he encontrado recientemente en esto migrando un viejo proyecto a Entity Framework. Como se mencionó, si tiene un tipo derivado de una entidad, no puede almacenarlo, solo el tipo de base. La solución fue un método de extensión con reflexión.

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

No es muy limpio, así que use esto si realmente no tiene otra opción.

+0

Muy buena solución cuando no hay otra forma. Útil para serializadores especiales, etc. –

+0

Funciona muy bien aquí. Tenía que modificar la línea if (sourceProp! = null) de la siguiente manera para comprobar que la propiedad de destino era escribible: if (sourceProp! = null && destinationProp.CanWrite) – Daren