Класс Type имеет метод IsAssignableFrom (), который почти работает. К сожалению, он возвращает true, только если два типа совпадают или первый находится в иерархии второго. В нем говорится, что десятичное число нельзя назначить из int, но я бы хотел метод, который бы указывал, что десятичные числа можно назначать из целых, но не всегда можно назначить целые числа из десятичных. Компилятор знает это, но мне нужно выяснить это во время выполнения.
Вот тест для метода расширения.
[Test]
public void DecimalsShouldReallyBeAssignableFromInts()
{
Assert.IsTrue(typeof(decimal).IsReallyAssignableFrom(typeof(int)));
Assert.IsFalse(typeof(int).IsReallyAssignableFrom(typeof(decimal)));
}
Есть ли способ реализовать IsReallyAssignableFrom (), который бы работал как IsAssignableFrom (), но также проходил тестовый пример выше? Этот пример не компилируется для меня, поэтому мне пришлось установить Number на 0 (вместо 0.0M).
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public class MyAttribute : Attribute
{
public object Default { get; set; }
}
public class MyClass
{
public MyClass([MyAttribute(Default= 0.0M)] decimal number)
{
Console.WriteLine(number);
}
}
Я получаю эту ошибку: Ошибка 4 Аргументом атрибута должно быть константное выражение, Выражение typeof или выражение создания массива типа параметра атрибута
Этот почти работает... он использует выражения Linq:
public static bool IsReallyAssignableFrom(this Type type, Type otherType)
{
if (type.IsAssignableFrom(otherType))
return true;
try
{
var v = Expression.Variable(otherType);
var expr = Expression.Convert(v, type);
return expr.Method == null || expr.Method.Name == "op_Implicit";
}
catch(InvalidOperationException ex)
{
return false;
}
}
Единственный случай, который не работает, это встроенные преобразования для примитивных типов: он неправильно возвращает true
для преобразований, которые должны быть явными (например, int
в short
). Я думаю, вы могли бы обрабатывать эти случаи вручную, так как их конечное (и довольно небольшое) количество.
Мне не очень нравится перехватывать исключение для обнаружения недопустимых преобразований, но я не вижу другого простого способа сделать это...
На самом деле бывает так, что тип decimal
не "присваивается" типу int
, и наоборот. Проблемы возникают, когда задействована упаковка/распаковка.
Возьмем пример ниже:
int p = 0;
decimal d = 0m;
object o = d;
object x = p;
// ok
int a = (int)d;
// invalid cast exception
int i = (int)o;
// invalid cast exception
decimal y = (decimal)p;
// compile error
int j = d;
Этот код выглядит так, как будто он должен работать, но приведение типа из объекта приводит к недопустимому исключению приведения, а последняя строка генерирует ошибку времени компиляции.
Причина, по которой присваивание a
работает, заключается в том, что класс decimal
имеет явное переопределение оператора приведения типа к int
. Не существует оператора неявного приведения типа от decimal
до int
.
Редактировать: Не существует даже обратного неявного оператора. Int32 реализует IConvertible, и именно так он преобразуется в десятичное число. Конец редактирования
Другими словами, типы не назначаемые, а преобразуемые.
Вы можете сканировать сборки на наличие явных операторов приведения типов и интерфейсов IConvertible, но у меня сложилось впечатление, что это не будет вам так же полезно, как программирование для тех немногих конкретных случаев, с которыми, как вы знаете, вы столкнетесь.
Удачи!
Ответ Тимви действительно полный, но я чувствую, что есть даже более простой способ, который даст вам та же семантика (проверьте «реальную» присваиваемость), фактически не определяя себе, что это является.
Вы можете просто попробовать задание и найти InvalidCastException
(я знаю, что это очевидно). Таким образом, вы избегаете хлопот проверки трех возможных значений присваиваемости, как упоминал Тимви. Вот пример использования xUnit:
[Fact]
public void DecimalsShouldReallyBeAssignableFromInts()
{
var d = default(decimal);
var i = default(i);
Assert.Throws<InvalidCastException)( () => (int)d);
Assert.DoesNotThrow( () => (decimal)i);
}