C# - является оператором - Проверить кастабильность для всех доступных преобразований
Отредактировано после прочтения, измененный вопрос, чтобы быть более конкретным.
Согласно документации Microsoft:
Выражение is оценивается как true, если предоставленное выражение не является нулевым, и предоставленный объект может быть приведен к предоставленному типу, не вызывая исключение. В противном случае выражение оценивается как ложное.
Вот проблема ниже.
var test = (Int32)(Int16)1; // Non-null and does not cause an exception.
var test2 = (Int16)1 is Int32; // Evaluates false.
В документации также говорится:
Обратите внимание, что оператор is рассматривает только ссылочные преобразования, преобразования в блокировку и преобразования в распаковку. Другие преобразования, такие как пользовательские преобразования, не рассматриваются.
Итак, я предполагаю, что это не работает выше, потому что это пользовательское преобразование.
Как я могу проверить, можно ли что-то преобразовать в другой тип, включая нереференсные / бокс / конверсионные преобразования?
Примечание. Я обнаружил эту проблему при написании модульных тестов для моего расширения CastToOrDefault, которое работает со всеми типами, включая нереферентные (по сравнению с as).
Рефакторированный ответ на основе связанного кода Майка Прекупа
Я исправил его ответ, потому что чувствовал, что он написан в старом стиле. Плюс, хотя это и медленнее, было несколько странно иметь таблицу с множеством повторяющихся значений, когда каждый список является просто подмножеством другого списка. Поэтому я сделал таблицу рекурсивной. Примечание: ThrowIfNull просто генерирует ArgumentNullException, если значение равно нулю.
private static readonly Dictionary<Type, IEnumerable<Type>> PrimitiveTypeTable = new Dictionary<Type, IEnumerable<Type>>
{
{ typeof(decimal), new[] { typeof(long), typeof(ulong) } },
{ typeof(double), new[] { typeof(float) } },
{ typeof(float), new[] { typeof(long), typeof(ulong) } },
{ typeof(ulong), new[] { typeof(uint) } },
{ typeof(long), new[] { typeof(int), typeof(uint) } },
{ typeof(uint), new[] { typeof(byte), typeof(ushort) } },
{ typeof(int), new[] { typeof(sbyte), typeof(short), typeof(ushort) } },
{ typeof(ushort), new[] { typeof(byte), typeof(char) } },
{ typeof(short), new[] { typeof(byte) } }
};
private static bool IsPrimitiveCastableTo(this Type fromType, Type toType)
{
var keyTypes = new Queue<Type>(new[] { toType });
while (keyTypes.Any())
{
var key = keyTypes.Dequeue();
if (key == fromType) { return true; }
if (PrimitiveTypeTable.ContainsKey(key)) { PrimitiveTypeTable[key].ToList().ForEach(keyTypes.Enqueue); }
}
return false;
}
/// <summary>
/// Determines if this type is castable to the toType.
/// This method does more than the is-operator and
/// allows for primitives and implicit/explicit conversions to be compared properly.
/// http://stackru.com/a/18256885/294804
/// </summary>
/// <param name="fromType">The type to cast from.</param>
/// <param name="toType">The type to be casted to.</param>
/// <returns>True if fromType can be casted to toType. False otherwise.</returns>
/// <exception cref="ArgumentNullException">Thrown if either type is null.</exception>
public static bool IsCastableTo(this Type fromType, Type toType)
{
// http://stackru.com/a/10416231/294804
return toType.ThrowIfNull().IsAssignableFrom(fromType.ThrowIfNull()) ||
fromType.IsPrimitiveCastableTo(toType) ||
fromType.GetMethods(BindingFlags.Public | BindingFlags.Static).Any(m =>
m.ReturnType == toType && m.Name == "op_Implicit" || m.Name == "op_Explicit");
}
2 ответа
Также со страницы MSDN вы ссылались:
Обратите внимание, что оператор is рассматривает только ссылочные преобразования, преобразования в блокировку и преобразования в распаковку. Другие преобразования, такие как пользовательские преобразования, не рассматриваются.
Поскольку неявное приведение определено для Int16
а также Int32
типы не являются ссылочным преобразованием, преобразованием в бокс или преобразованием в распаковку, is
оценивается как ложное.
Если вам интересно, почему это не ссылочное преобразование, обратите внимание на следующее со страницы о неявных ссылочных преобразованиях:
Ссылочные преобразования, неявные или явные, никогда не изменяют ссылочную идентичность преобразуемого объекта. Другими словами, хотя преобразование ссылки может изменить тип ссылки, оно никогда не изменяет тип или значение объекта, на который ссылаются.
Поскольку тип изменяется, и это не просто приведение к суперклассу, это не считается ссылочным преобразованием.
РЕДАКТИРОВАТЬ: Чтобы ответить на второй вопрос, который вы редактировали, просмотрите эту страницу. Я скопирую код для справки:
static class TypeExtensions {
static Dictionary<Type, List<Type>> dict = new Dictionary<Type, List<Type>>() {
{ typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
{ typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
{ typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
{ typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
{ typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
{ typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } },
{ typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
{ typeof(ushort), new List<Type> { typeof(byte), typeof(char) } },
{ typeof(short), new List<Type> { typeof(byte) } }
};
public static bool IsCastableTo(this Type from, Type to) {
if (to.IsAssignableFrom(from)) {
return true;
}
if (dict.ContainsKey(to) && dict[to].Contains(from)) {
return true;
}
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Any(
m => m.ReturnType == to &&
m.Name == "op_Implicit" ||
m.Name == "op_Explicit"
);
return castable;
}
}
Код использует Reflection, чтобы проверить, существуют ли какие-либо неявные или явные приведения. Однако метод отражения не работает с примитивами, поэтому проверка должна выполняться вручную, поэтому Dictionary
с возможными приведениями для примитивов.
Согласно MSDN также -
Ключевое слово is вызывает предупреждение во время компиляции, если известно, что выражение всегда истинно или всегда ложно, но обычно оценивает совместимость типов во время выполнения.
И это легко увидеть в предупреждении -
И @Mike уже указывал на то, почему он оценивается как false
,