Насколько я понимаю, следующий код будет всегда компилироваться и, кроме того, всегда будет давать сбой во время выполнения, вызывая ошибку InvalidCastException.
Пример:
public class Post { }
public class Question : Post { }
public class Answer : Post
{
public void Fail()
{
Post p = new Post();
Question q = (Question)p; // This will throw an InvalidCastException
}
}
Мои вопросы...
Если мои предположения неверны, то может ли кто-нибудь привести пример, демонстрирующий, как они неверны?
Если мои предположения верны, то почему компилятор не предупреждает об этой ошибке?
Почему вы ожидаете, что компилятор будет следовать всем возможным путям кода, чтобы определить, что p не был изменен до приведения? John Saunders 17.11.2011
И событие, если оно не изменилось, Post может реализовать неявный оператор для приведения себя к Question или наоборот. PVitt 17.11.2011
броски для ковбоев, садись на быка и катайся, детка kenny 17.11.2011
Как указывали другие, статический анализ для определения того, что это не удастся, нетривиален. Некоторые из более новых инструментов статического анализа найдут подобные вещи, но они достаточно сложны, чтобы не выпадать естественным образом из-за ошибки во время компиляции. Dan Bryant 17.11.2011
Есть несколько причин, по которым это преобразование разрешено.
Во-первых, как говорили люди в других ответах, оператор приведения означает, что я знаю больше, чем вы; Я гарантирую вам, что это преобразование пройдет успешно, и если я ошибаюсь, выкину исключение и завершу процесс. Если вы лжёте компилятору, произойдут плохие вещи; на самом деле вы не даете эту гарантию, и в результате программа выходит из строя.
Теперь, если компилятор может сказать, что вы ему лжете, он может уличить вас во лжи. Компилятору не требуется быть сколь угодно умным, чтобы поймать вас на вашей лжи! Анализ потока, необходимый для определения того, что выражение типа Base никогда не будет иметь тип Derived, сложен; значительно сложнее, чем логика, которую мы уже реализуем для обнаружения таких вещей, как неназначенные локальные переменные. У нас есть лучшие способы потратить наше время и усилия, чем улучшать способность компилятора уличать вас в очевидной лжи.
Поэтому компилятор обычно анализирует только типы выражений, а не возможные значения. Только из анализа типов невозможно узнать, будет ли преобразование успешным. Это может завершиться успешно, поэтому это разрешено. Запрещены только те приведения, о которых компилятор знает, что они всегда будут неудачными в результате анализа типов.
Во-вторых, можно указать (Derived)(new Base()), где Derived - это тип, который реализует тип Base и который не дает сбой во время выполнения. Также возможен сбой (Base)(new Base()) с недопустимым исключением приведения во время выполнения! Реальные факты! Это чрезвычайно редкие ситуации, но они возможны.
В некоторых случаях Post может быть преобразовано в Question. Выполняя приведение, вы говорите компилятору: "Это сработает, я обещаю. Если нет, вы можете сгенерировать недопустимое исключение приведения".
Например, этот код будет работать нормально:
Post p = new Question();
Question q = (Question)p;
Приведение прямо заявляет, что вы лучше, чем компилятор, знаете, что это такое на самом деле. Возможно, вы захотите сделать что-то вроде ключевых слов as или is?
Ответ 3 · рейтинг 8 · Paolo Tedesco
Дело в том, что p может быть Question, поскольку вопрос наследуется от Post. Рассмотрим следующее:
public class Post { }
public class Question : Post { }
public class Banana { }
static class Program {
public static void Main(params string[] args) {
Post p = new Question();
Question q = (Question)p; // p IS a Question in this case
Banana b = (Banana)p; // this does not compile
}
}
Ответ 4 · рейтинг 6 · Oded
Когда вы выполняете явное приведение типов, вы говорите компилятору: "Я знаю то, чего не знаешь ты".
По сути, вы переопределяете обычную логику компилятора - pможет быть Question (поэтому компилятор скомпилируется), вы сообщаете компилятору, что вы знаете, что это (хотя это и не так, следовательно, исключение во время выполнения).
Ответ 5 · рейтинг 2 · Justin Niessner
1) Ваше предположение неверно. Кто-нибудь всегда может реализовать явный оператор преобразования для Question для преобразования из Post:
public class Question`
{
// some class implementation
public static explicit operator Question(Post p)
{
return new Question { Text = p.PostText };
}
}
2) Явное приведение - это ваш способ сообщить компилятору, что вы знаете лучше, чем он. Если вы хотите что-то использовать, когда вы не уверены, будет ли приведение успешным или нет, и не хотите исключения во время выполнения, используйте операторы is и as.
Ответ 6 · рейтинг 1 · Meligy
Компилятор рассматривает p как переменную, поэтому не пытается отслеживать ее значение. Если бы это было так, анализ всего приложения занял бы очень много времени. Некоторые инструменты статического анализа похожи на FxCop.
Компилятор видит Post, но не отслеживает присваивание и знает, что возможно:
Post p = new Question();
Так что проходит нормально.
Вы знаете, что не можете сделать:
Question q = p;
Разница в том, что здесь вы пытаетесь сказать компилятору использовать то, что он знает, для проверки этого, и он знает, что Post не обязательно является Question.
В исходной версии вы говорите компилятору: "Я знаю, что это так, и я установлю это явно, уйду с дороги, и я возьму исключение, если то, что я знаю, неверно", поэтому он слушает вас и выходит твой путь!
Ответ 7 · faester
Ваши предположения верны: он скомпилируется и завершится ошибкой во время выполнения.
В вашем небольшом примере очевидно, что приведение потерпит неудачу, но компилятор не знает об этом. Поскольку Post является супертипом Question, вы можете присвоить Questionp, и, поскольку вы делаете приведение, вы объявляете о готовности взять на себя некоторую ответственность от компилятора. Если бы вы пытались назначить string или что-то еще, не являющееся частью той же ветки наследования, компилятор должен вас предупредить. И наоборот, вы всегда можете попытаться привести object к любому типу.
Но если компилятор жалуется на ваш конкретный пример, это будет означать, что никакие приведения не будут разрешены.
Ответ 8 · bygrace
Вау, Джереми, я недавно столкнулся с этой проблемой! Поэтому я сделал этот удобный метод расширения, который сопоставляет две модели с несколькими одинаковыми свойствами. Намерение состояло в том, чтобы использовать его, когда класс A наследует от класса B, чтобы сопоставить класс B с классом A. Надеюсь, вы найдете это полезным!
public static class ObjectHelper
{
public static T Cast<T>(this Object source)
{
var destination = (T)Activator.CreateInstance(typeof(T));
var sourcetype = source.GetType();
var destinationtype = destination.GetType();
var sourceProperties = sourcetype.GetProperties();
var destionationProperties = destinationtype.GetProperties();
var commonproperties = from sp in sourceProperties
join dp in destionationProperties on new { sp.Name, sp.PropertyType } equals
new { dp.Name, dp.PropertyType }
select new { sp, dp };
foreach (var match in commonproperties)
{
match.dp.SetValue(destination, match.sp.GetValue(source, null), null);
}
return destination;
}
}
К вашему сведению, это, вероятно, будет работать, только если два объекта существуют в одной сборке.