web-dev-qa-db-fra.com

InvalidCastException: impossible de transtyper des objets de type [base] à taper [sous-classe]

J'ai un CustomMembershipUser personnalisé qui hérite de MembershipUser.

public class ConfigMembershipUser : MembershipUser
{
    // custom stuff
}

J'utilise Linq-to-SQL pour lire à partir d'une base de données et obtenir une entité utilisateur; pour rendre cette fonction en tant qu'abonné MembershipUser, j'ai défini une conversion explicite:

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

Ce casting fonctionne bien: 

MembershipUser memUser = (MembershipUser) entityUser;

Cependant, la deuxième conversion vers CustomMembershipUser échoue:

MembershipUser memUser = (MembershipUser) entityUser;
CustomMembershipUser custUser = (CustomMembershipUser) memUser;

Si je change le casting en 

CustomMembershipUser custUser = memUser;

Je reçois une erreur intellisense me disant qu'une distribution implicite ne fonctionnera pas mais une distribution explicite existe.

... et pour couronner le tout, je ne peux apparemment pas définir un transtypage d'une classe de base à une sous-classe. J'ai essayé et ça a échoué. Ce que je ne comprends pas le plus souvent, c’est pourquoi un transtypage d’une classe de base vers une sous-classe ever fail? Par définition, la sous-classe a toutes les propriétés de la classe de base, alors quel est le problème.

MODIFIER

J'ai essayé de définir une conversion explicite de MembershipUser à CustomMembershipUser (j'ai d'abord défini un constructeur privé pour la distribution):

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
    }

Ensuite, j'ai défini mon casting personnalisé:

public static explicit operator CustomMembershipUser(MembershipUser user)
{
     return new CustomMembershipUser(user);
}

et j'ai eu l'erreur suivante:

'CustomMembershipUser.explicit, opérateur CustomMembershipUser (System.Web.Security.MembershipUser) ': conversions définies par l'utilisateur vers ou depuis une classe de base ne sont pas autorisés

Donc ... je ne peux pas transtyper d'une classe de base vers une sous-classe? 

21
Scott Baker

Vous avez le contraire: un transtypage d'un objet d'une classe de base vers une sous-classe échouera toujours, car la classe de base n'a que seulement les propriétés de la classe de base (pas la sous-classe). .

Comme, comme vous le dites, la sous-classe a toutes les propriétés de la classe de base (c'est "un objet de la classe de base"), la conversion de la sous-classe vers la classe de base réussira toujours, mais jamais l'inverse.

En d'autres termes, vous pouvez considérer tous les léopards comme des chats, mais vous ne pouvez pas prendre un chat arbitraire et le traiter comme un léopard (à moins que ce ne soit déjà un léopard au départ).

Vous devez soit renvoyer un objet CustomMembershipUser au lieu d’un objet MembershipUser, ou définir un autre distribution explicite fonction distincte qui convertit MembershipUsers en CustomMembershipUsers en créant un nouvel objet CustomMembershipUser. Vous ne pouvez pas obtenir un objet CustomMembershipUser de nulle part; il a d'abord été créé, directement ou en instanciant une sous-classe de CustomMembershipUser (pas une classe de base).

Modifier:

J'avais tort de définir une distribution explicite dans la sous-classe. Ce n'est pas possible (comme le montre l'erreur que vous voyez). Vous semblez maintenant être dans la même situation que le demandeur de cette question . Le casting n’est pas vraiment la solution; créez d’abord des objets CustomMembershipUser (utilisables directement en tant qu’objets MembershipUser) ou écrivez une méthode de conversion qui accepte une MembershipUser et crée une CustomMembershipUser.

Le transtypage d'un objet de base vers un objet de sous-classe n'a de sens que lorsqu'il s'agit déjà d'un objet de sous-classe (mais que la variable le contenant est du type classe de base).

30
Cameron

Une variable de type MembershipUser peut contenir un objet de type CustomMembershipUser, car le sous-type est une instance du supertype. Mais l'inverse n'est pas vrai.

CustomMembershipUser peut avoir des membres qui ne sont pas sur MembershipUser. Par conséquent, une variable de type CustomMembershipUser ne peut pas contenir un objet de type MembershipUser. Sinon, le code peut essayer d'accéder à l'un de ces membres qu'il ne contient pas.

Cela échoue:

CustomMembershipUser custUser = memUser; 

parce que vous pouvez suivre avec ceci:

custUser.CustomStuff();   // Oops! Can't call CustomStuff() on a MembershipUser object!

Message "Explicit Cast Exists"  

La raison pour laquelle vous recevez le message "une distribution explicite existe" n'est pas due au fait que vous avez créé une conversion de User à MembershipUser. (Le type d'utilisateur n'est pas impliqué du tout ici.) C'est parce qu'un casting explicite always existe d'un supertype à sous-type. Cela fait partie de la conception du langage. C'est pour prendre en charge le scénario dans lequel vous savez que l'objet est du sous-type et que vous souhaitez utiliser une variable qui correspond. Mais si vous utilisez cette distribution explicite sur un objet qui n'est pas du type cible, vous obtenez alors une erreur d'exécution (comme vous l'avez constaté).

Plus d'explications sur les raisons de l'échec de la distribution

En C #, chaque objet a un type. Ce type ne peut jamais être changé pour la durée de vie de l'objet. Une fois que vous avez créé un employé (par exemple), il s'agira toujours d'un employé pour toujours et à jamais, ou jusqu'à la collecte des ordures, amen.

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 vous avez une variable de type Personne, cette variable peut contenir un objet Employé, car un employé est une personne.

Employee mike = new Employee("Michael", DateTime.Now);
Person myBestBud = mike;

Ceci est une distribution illicite, car cela fonctionne toujours. Une variable Personne peut toujours contenir un objet Employé. La raison en est que le système sait que chaque membre de la personne qu'il essaie d'utiliser sera disponible, à cause de l'héritage.

Console.WriteLine("Dude's name: " + myBestBud.Name);

Essayons maintenant dans l'autre sens.

Person johnny = new Person("Johnny Johnson");
Employee newHire = johnny;  // ERROR - Attempt to assign...etc.  An explicit cast is available...

Cela provoque une erreur. Il n'y a pas de conversion implicite de Personne à Employé, car le compilateur ne peut pas garantir qu'une variable Personne contient un objet Employé. Cela provoque donc une erreur lors de la compilation. Alors, essayons la distribution explicite.

Employee newHire = (Employee)johnny;

Cela compilera très bien. Ceci est autorisé par le compilateur, car parfois une variable Personne contiendra un objet Employé. Mais cela échouera au moment de l'exécution. La raison pour laquelle cela échouera, c'est parce que la variable johnny n'a pas d'employé, elle ne peut donc pas être traitée comme tel. Donc, une exception de distribution invalide est levée.

S'il ne lève pas une exception de distribution invalide, nous pourrions alors essayer de faire quelque chose comme ceci:

Console.WriteLine("Hired on: " + newHire.HireDate);

Mais la propriété n'existe pas, car l'objet est vraiment une personne et non un employé.

Vous pouvez donc voir qu'il y a une conversion implicite d'un sous-type à un autre, parce que cela réussit toujours et ne pose aucun problème. Il y a une conversion explicite de super-type en sous-type, car cela ne fonctionne que si le type à l'exécution de l'objet est une affectation compatible avec la variable. On s'attend à ce que le programmeur sache quand cela fonctionne et quand cela ne fonctionne pas, et ne fait le casting que quand cela fonctionnera. Sinon, le moteur d'exécution détectera la distribution non valide et lèvera une exception.

Désormais, un utilisateur peut parfois créer un opérateur de conversion personnalisé pouvant être utilisé pour convertir un type en un autre. Lorsque cela se produit, un objet entièrement nouveau du type cible est créé. Cependant, ceci ne peut pas être monté ou descendu dans une hiérarchie d'héritage, car les transtypages pour ceux-ci sont déjà fournis par le compilateur C #. Pour créer un opérateur de conversion personnalisé, le type source ou cible ne doit pas être un ancêtre ni un être dépendant de l'autre type.

9

Il est possible de faire un cast, mais vous devez désintégrer l'objet. La meilleure façon de le faire est de créer un appel à lui-même qui retourne le même objet, comme décrit ici: Issue avec des mandataires lors de l'utilisation de la table NHibernate stratégie d'héritage par sous-classe

0
Shagglez