c# - studio - InvalidCastException: no se puede convertir objetos de tipo
programacion orientada a objetos c# (3)
Tengo un CustomMembershipUser personalizado que hereda de MembershipUser.
public class ConfigMembershipUser : MembershipUser
{
// custom stuff
}
Estoy usando Linq-to-SQL para leer desde una base de datos y obtener una entidad de usuario; para hacer que esta función sea un Usuario de Membresía, he definido una conversión explícita:
public static explicit operator MembershipUser(User user)
{
DateTime now = DateTime.Now;
if (user == null) return null;
return new MembershipUser("MagicMembershipProvider",
user.DisplayName, user.Id,
user.Email, "", "", true, false,
now, now, now, now, now);
}
Este elenco funciona bien:
MembershipUser memUser = (MembershipUser) entityUser;
Sin embargo, el segundo lanzamiento a CustomMembershipUser falla:
MembershipUser memUser = (MembershipUser) entityUser;
CustomMembershipUser custUser = (CustomMembershipUser) memUser;
Si cambio el modelo a
CustomMembershipUser custUser = memUser;
Recibo un error de intellisense que me dice que un lanzamiento implícito no funcionará, pero existe un molde explícito .
... y para colmo, aparentemente no puedo definir un elenco de una clase base a una subclase. Lo intenté y falló. Lo que más no entiendo es por qué un elenco de una clase base a una subclase puede fallar alguna vez ? Por definición, la subclase tiene todas las propiedades de la clase base, ¿cuál es el problema?
EDITAR
Traté de definir una conversión explícita de MembershipUser a CustomMembershipUser (primero definí un constructor privado para el elenco):
private ConfigMembershipUser(MembershipUser user)
: base(user.ProviderName, user.UserName, user.ProviderUserKey, user.Email,
user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut,
user.CreationDate, user.LastLoginDate, user.LastActivityDate,
user.LastPasswordChangedDate, user.LastLockoutDate)
{
// initialize extended CustomMembershipUser stuff here
}
Luego definí mi elenco personalizado:
public static explicit operator CustomMembershipUser(MembershipUser user)
{
return new CustomMembershipUser(user);
}
y recibí el siguiente error:
''CustomMembershipUser.explicit operator CustomMembershipUser (System.Web.Security.MembershipUser)'': las conversiones definidas por el usuario hacia o desde una clase base no están permitidas .
Entonces ... ¿No puedo convertir una clase base en una subclase?
Una variable de tipo MembershipUser puede contener un objeto de tipo CustomMembershipUser, porque el subtipo es una instancia del supertipo. Pero la conversación no es verdadera.
CustomMembershipUser podría tener miembros que no estén en MembershipUser. Por lo tanto, una variable de tipo CustomMembershipUser no puede contener un objeto de tipo MembershipUser. De lo contrario, el código puede intentar acceder a uno de esos miembros que no contiene.
Esto falla:
CustomMembershipUser custUser = memUser;
porque puedes seguirlo con esto:
custUser.CustomStuff(); // Oops! Can''t call CustomStuff() on a MembershipUser object!
Mensaje "Explicit Cast Exists"
La razón por la que recibes el mensaje "existe un envío explícito" no se debe a que hayas creado un elenco de usuario a usuario de membresía. (El tipo de Usuario no está involucrado aquí). Se debe a que siempre existe un molde explícito desde un supertipo a subtipo. Eso es parte del diseño del lenguaje. Esto es para apoyar el escenario en el que sabe que el objeto es del subtipo y desea usar una variable que coincida. Pero si usa esa conversión explícita en un objeto que no es del tipo de destino, entonces obtiene un error de tiempo de ejecución (como lo ha experimentado).
Más explicaciones sobre por qué falla el reparto
En C # cada objeto tiene un tipo. Ese tipo nunca se puede cambiar durante la vida del objeto. Una vez que crea un empleado (por ejemplo), siempre será un empleado por siempre, o hasta la recolección de basura, amén.
public class Person
{
public string Name {get; private set;}
public Person(string name)
{ Name = name; }
}
public class Employee : Person
{
public DateTime HireDate {get; private set;}
public Employee(string name, DateTime hireDate)
: base (name)
{ HireDate = hireDate; }
}
Si tiene una variable de tipo Persona, entonces esa variable puede contener un objeto Empleado, porque un Empleado es una Persona.
Employee mike = new Employee("Michael", DateTime.Now);
Person myBestBud = mike;
Este es un elenco implícito, porque siempre funciona. Una variable de Persona siempre puede contener un objeto Empleado. La razón de esto es porque el sistema sabe que cada miembro de la persona que intenta usar estará disponible debido a la herencia.
Console.WriteLine("Dude''s name: " + myBestBud.Name);
Ahora, intentémoslo de otra manera.
Person johnny = new Person("Johnny Johnson");
Employee newHire = johnny; // ERROR - Attempt to assign...etc. An explicit cast is available...
Esto causa un error. No existe un elenco implícito de Persona a Empleado, porque el compilador no puede garantizar que una variable de Persona contenga un objeto Empleado. Entonces eso causa un error en tiempo de compilación. Entonces, probemos el lanzamiento explícito.
Employee newHire = (Employee)johnny;
Esto compilará muy bien. Esto es permitido por el compilador, porque a veces una variable Person tendrá un objeto Employee. Pero esto fracasará en tiempo de ejecución. La razón por la cual esto fracasará es porque la variable johnny no tiene un empleado, por lo que no puede tratarse como tal. Por lo tanto, se lanza una excepción de conversión inválida.
Si no arrojó una excepción de conversión inválida, entonces podríamos tratar de hacer algo como esto:
Console.WriteLine("Hired on: " + newHire.HireDate);
Pero la propiedad no existe, porque el objeto es realmente una Persona, no un Empleado.
Entonces puede ver que hay un molde implícito de subtipo a supertipo, porque eso siempre tiene éxito y no causa problemas. Hay una conversión explícita de supertipo a subtipo, porque eso solo funciona si el tipo de tiempo de ejecución del objeto es compatible con la asignación de la variable. Se espera que el programador sepa cuándo funciona y cuándo no, y solo lo hará cuando funcione. De lo contrario, el tiempo de ejecución detectará el lanzamiento no válido y lanzará una excepción.
Ahora, a veces, un usuario puede crear un operador de conversión personalizado que se puede usar para transmitir de un tipo a otro. Cuando eso sucede, se crea un objeto completamente nuevo del tipo de destino. Sin embargo, esto no se puede hacer arriba o abajo de una jerarquía de herencia, porque los moldes para esos ya están provistos por el compilador de C #. Para realizar un operador de conversión personalizado, el tipo de origen o destino no debe ser un antepasado o descendencia del otro tipo.
Es posible crear un elenco, sin embargo, debe delegar el proxy del objeto, la manera más fácil de hacerlo es crear una llamada a sí mismo que devuelva el mismo objeto, como se describe aquí: Emita con proxies de Casting cuando use la tabla NHibernate por subclase estrategia de herencia
Lo tienes al revés: un lanzamiento desde un objeto de una clase base a una subclase siempre fallará, porque la clase base tiene solo las propiedades de la clase base (no la subclase).
Como, como dices, la subclase tiene todas las propiedades de la clase base (es un objeto de clase base), entonces un lanzamiento de la subclase a la clase base siempre tendrá éxito, pero nunca al revés.
En otras palabras, puedes pensar en todos los leopardos como gatos, pero no puedes tomar un gato arbitrario y tratarlo como un leopardo (a menos que ya sea un leopardo para empezar).
CustomMembershipUser
devolver un objeto CustomMembershipUser
lugar de un objeto MembershipUser
o definir otra función de conversión explícita para convertir MembershipUsers a CustomMembershipUsers creando un nuevo objeto CustomMembershipUser
. No puede obtener un objeto CustomMembershipUser de la nada; se ha creado primero, ya sea directamente o creando una subclase de CustomMembershipUser
(no una clase base).
Editar:
Me equivoqué al definir un lanzamiento explícito a la subclase. Esto no es posible (como indica el error que ves). Ahora pareces estar exactamente en la misma situación que el que hace esta pregunta . Casting no es realmente el camino a seguir aquí, ya sea para crear objetos CustomMembershipUser
para empezar (que se pueden usar directamente como objetos MembershipUser
), o escribir un método de conversión que acepte un MembershipUser
y cree un CustomMembershipUser
.
La única vez que tiene sentido lanzar desde un objeto base a un objeto de una subclase es cuando ya es un objeto de una subclase (pero la variable que lo contiene es del tipo de clase base).