InvalidCastException: невозможно преобразовать объекты типа [base] в тип [subclass] - PullRequest
21 голосов
/ 09 марта 2011

У меня есть пользовательский CustomMembershipUser, который наследуется от MembershipUser.

public class ConfigMembershipUser : MembershipUser
{
    // custom stuff
}

Я использую Linq-to-SQL для чтения из базы данных и получения сущности User;чтобы сделать эту функцию в качестве MembershipUser, я определил явное преобразование:

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

Это приведение работает нормально:

MembershipUser memUser = (MembershipUser) entityUser;

Однако, второе приведение к CustomMembershipUser завершается неудачно:

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

Если я изменю приведение на

CustomMembershipUser custUser = memUser;

, я получаю ошибку в виде intellisense, сообщающую, что неявное приведение не будет работать , но существует явное приведение .

... и, в довершение всего, я не могу определить приведение из базового класса в подкласс.Я попробовал это, и это не удалось.Больше всего я не понимаю, что почему приведение из базового класса к подклассу когда-либо завершится неудачей ?По определению у подкласса есть все свойства базового класса, в чем проблема.

EDIT

Я попытался определить явное приведение из MembershipUser к CustomMembershipUser (сначала я определил личный конструктор для приведения):

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
    }

Затем я определил свой пользовательский прилив:

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

и получил следующую ошибку:

'CustomMembershipUser.explicit оператор CustomMembershipUser (System.Web.Security.MembershipUser)': пользовательские преобразования в или из базового класса не допускаются .

Итак ... Я не могу привести из базового класса в подкласс?

Ответы [ 3 ]

31 голосов
/ 09 марта 2011

У вас получилось наоборот: приведение объекта базового класса к подклассу будет всегда неудачным, потому что базовый класс имеет только свойства объекта. базовый класс (не подкласс).

Поскольку, как вы говорите, у подкласса есть все свойства базового класса (он является объектом базового класса), преобразование из подкласса в базовый класс всегда будет успешным, но никогда не будет обратным .

Другими словами, вы можете думать о всех леопардах как о кошках, но вы не можете взять произвольную кошку и обращаться с ней как с леопардом (если только это уже не леопард).

Вам нужно либо вернуть объект CustomMembershipUser вместо объекта MembershipUser, либо определить другую явную приведение отдельной функции, которая преобразует MembershipUsers в CustomMembershipUsers путем создания нового объекта CustomMembershipUser. Вы не можете получить объект CustomMembershipUser из ниоткуда; сначала он создается либо напрямую, либо путем создания экземпляра подкласса CustomMembershipUser (не базового класса).

Edit:

Я ошибся в определении явного приведения к подклассу. Это невозможно (как показывает ошибка, которую вы видите). Теперь вы, похоже, находитесь в той же ситуации, что и вопросник этого вопроса . Кастинг на самом деле не является подходящим способом - либо создайте CustomMembershipUser объекты для начала (которые можно использовать непосредственно как MembershipUser объекты), либо напишите метод преобразования, который принимает MembershipUser и создает CustomMembershipUser.

Единственный раз, когда имеет смысл преобразовать базовый объект в объект подкласса, это когда уже объект подкласса (но переменная, содержащая его, имеет тип базового класса).

10 голосов
/ 09 марта 2011

Переменная типа MembershipUser может содержать объект типа CustomMembershipUser, поскольку подтип является экземпляром супертипа.Но обратное неверно.

В CustomMembershipUser могут быть члены, которых нет в MembershipUser.Поэтому переменная типа CustomMembershipUser не может содержать объект типа MembershipUser.В противном случае код может попытаться получить доступ к одному из тех членов, которых он не содержит.

Сбой:

CustomMembershipUser custUser = memUser; 

, поскольку вы можете выполнить его следующим образом:

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

Сообщение «явное приведение существует»

Причина, по которой вы получаете сообщение «явное приведение существует», не в том, что вы создали приведение от пользователя к MembershipUser.(Тип User здесь вообще не задействован.) Это потому, что явное приведение всегда существует от супертипа до подтипа.Это часть языкового дизайна.Это необходимо для поддержки сценария, в котором вы знаете , что объект относится к подтипу, и вы хотите использовать соответствующую переменную.Но если вы используете это явное приведение к объекту, который не относится к целевому типу, вы получите ошибку времени выполнения (как вы уже видели).

Дальнейшее объяснение того, почему приведение не удается

В C # каждый объект имеет тип.Этот тип никогда не может быть изменен за время существования объекта.После того как вы создадите Employee (например), он всегда будет Employee навсегда или до момента сбора мусора, аминь.

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

Если у вас есть переменная типа Person, то эта переменная может содержатьобъект Employee, потому что Employee - это Person.

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

Это неявное приведение, потому что оно всегда работает.Переменная Person может всегда содержать объект Employee.Причина этого в том, что система знает, что каждый член Person, который она пытается использовать, будет доступен из-за наследования.

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

Теперь давайте попробуем это по-другому.

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

Это вызывает ошибку.Не существует неявного приведения от Person к Employee, потому что компилятор не может гарантировать, что переменная Person содержит объект Employee.Так что это вызывает ошибку во время компиляции.Итак, давайте попробуем явное приведение.

Employee newHire = (Employee)johnny;

Это скомпилируется просто отлично.Это разрешено компилятором, потому что иногда переменная Person будет содержать объект Employee.Но это не удастся во время выполнения.Причина, по которой это не удастся, заключается в том, что переменная johnny не имеет сотрудника, поэтому ее нельзя рассматривать как единицу.Таким образом, выдается недопустимое исключение приведения.

Если оно не выдало недопустимое исключение приведения, то мы можем попытаться сделать что-то вроде этого:

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

Но свойство несуществует, потому что объект на самом деле является человеком, а не сотрудником.

Таким образом, вы можете видеть, что происходит преобразование имплика из подтипа в супертип, потому что это всегда успешно и не вызывает проблем.Существует явное приведение от супертипа к подтипу, потому что это работает, только если тип времени выполнения объекта совместим по присваиванию с переменной.Предполагается, что программист знает, когда он работает, а когда нет, и выполняет приведение только тогда, когда он будет работать.В противном случае среда выполнения обнаружит недопустимое приведение и выдаст исключение.

Теперь иногда пользователь может создать пользовательский оператор преобразования, который можно использовать для преобразования из одного типа в другой.Когда это происходит, то создается совершенно новый объект целевого типа.Однако это не может быть сделано вверх или вниз по иерархии наследования, потому что приведение типов к ним уже осуществляется компилятором C #.Для выполнения пользовательского оператора преобразования исходный или целевой тип не должен быть предком или потомком другого типа.

0 голосов
/ 23 мая 2012

Можно выполнить приведение, однако вам необходимо разархивировать объект, самый простой способ сделать это - создать для себя вызов, который возвращает тот же объект, как описано здесь: Проблема с прокси-компонентами Cast при использовании таблицы NHibernate для стратегии наследования подкласса

...