InvalidCastException: невозможно преобразовать объекты типа [base] в тип [subclass]
У меня есть пользовательский 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, сообщающую, что неявное приведение не будет работать, но существует явное приведение.
... и в довершение всего, я не могу определить приведение из базового класса в подкласс. Я попробовал это, и это не удалось. Больше всего я не понимаю, почему произойдет сбой приведения из базового класса в подкласс? По определению подкласс имеет все свойства базового класса, так в чем же проблема.
РЕДАКТИРОВАТЬ
Я попытался определить явное приведение из 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 operator CustomMembershipUser (System.Web.Security.MembershipUser)': определенные пользователем преобразования в базовый класс или из него запрещены.
Итак... Я не могу привести из базового класса в подкласс?
3 ответа
Вы получаете все наоборот: приведение объекта базового класса к подклассу всегда будет неудачным, поскольку базовый класс имеет только свойства базового класса (но не подкласса).
Поскольку, как вы говорите, у подкласса есть все свойства базового класса (он является объектом базового класса), преобразование из подкласса в базовый класс всегда будет успешным, но никогда не будет обратным.
Другими словами, вы можете думать обо всех леопардах как о кошках, но вы не можете взять произвольную кошку и обращаться с ней как с леопардом (если только это уже не леопард).
Вам нужно либо вернуть CustomMembershipUser
объект вместо MembershipUser
объект или определить другую явную приведенную отдельную функцию, которая преобразует MembershipUsers в CustomMembershipUsers путем создания нового CustomMembershipUser
объект. Вы не можете получить объект CustomMembershipUser из ниоткуда; он был создан первым, либо напрямую, либо путем создания экземпляра подкласса CustomMembershipUser
(не базовый класс).
Редактировать:
Я ошибся в определении явного приведения к подклассу. Это невозможно (как показывает ошибка, которую вы видите). Теперь вы, похоже, находитесь в той же ситуации, что и тот, кто задает этот вопрос. Кастинг на самом деле не способ пойти сюда - либо создать CustomMembershipUser
объекты для начала (которые могут быть использованы как MembershipUser
объекты), или написать метод преобразования, который принимает MembershipUser
и создает CustomMembershipUser
,
Единственный раз, когда имеет смысл преобразовывать базовый объект в объект подкласса, это когда он уже является объектом подкласса (но переменная, в которой он содержится, относится к типу базового класса).
Переменная типа 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#. Чтобы выполнить пользовательский оператор преобразования, исходный или целевой тип не должен быть предком или потомком другого типа.
Можно выполнить приведение, однако вам необходимо разгрузить прокси-объект, самый простой способ сделать это - создать для себя вызов, возвращающий тот же объект, как описано здесь: Проблема с прокси-серверами при использовании таблицы NHibernate для подкласса стратегия наследования