Как проверить назначение типов во время выполнения на С#?

Класс Type имеет метод IsAssignableFrom(), который почти работает. К сожалению, он возвращает true только в том случае, если два типа являются одинаковыми или первые находятся в иерархии второго. В нем говорится, что десятичное значение не может быть назначено из int, но мне нужен метод, который указывает, что десятичные знаки назначаются из ints, но ints не всегда назначаются из десятичных знаков. Компилятор знает это, но мне нужно понять это во время выполнения.

Вот тест для метода расширения.

    [Test]
    public void DecimalsShouldReallyBeAssignableFromInts()
    {
        Assert.IsTrue(typeof(decimal).IsReallyAssignableFrom(typeof(int)));
        Assert.IsFalse(typeof(int).IsReallyAssignableFrom(typeof(decimal)));
    }

Есть ли способ реализовать IsReallyAssignableFrom(), который будет работать как IsAssignableFrom(), но также проходит тестовый пример выше?

Спасибо!

Edit:

Это в основном способ, которым он будет использоваться. Этот пример не компилируется для меня, поэтому мне пришлось установить 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 Аргумент атрибута должен быть константным выражением, выражением типаof или выражением создания массива типа параметра атрибута

Ответ 1

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

  • Иерархия классов, реализация интерфейса, ковариация и контравариантность. Это то, что уже проверяет .IsAssignableFrom. (Это также включает допустимые операции бокса, например от int до object или DateTime до ValueType.)

  • Пользовательские неявные преобразования. Это то, на что ссылаются все остальные ответы. Вы можете получить их через Reflection, например, неявное преобразование с int в decimal является статическим методом, который выглядит следующим образом:

    System.Decimal op_Implicit(Int32)
    

    Вам нужно только проверить два соответствующих типа (в этом случае Int32 и decimal); если преобразование не в них, то оно не существует.

  • Встроенные неявные преобразования, которые определены в Спецификация языка С#. К сожалению, Reflection не показывает эти. Вам придется найти их в спецификации и скопировать правила назначения в свой код вручную. Это включает в себя числовые преобразования, например. int до long, а также от float до double, конверсии указателей, преобразования с нулевым значением (int to int?) и отменено преобразования.

Кроме того, пользовательское неявное преобразование может быть закодировано с помощью встроенного неявного преобразования. Например, если пользовательское неявное преобразование существует от int до некоторого типа T, то оно также удваивается как преобразование из short в T. Аналогично, T to short удваивается как T до int.

Ответ 2

Этот почти работает... он использует выражения 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 to short). Я думаю, вы могли бы обрабатывать эти случаи вручную, так как существует конечное (и довольно небольшое) число из них.

Мне не очень нравится ловить исключение, чтобы обнаружить недопустимые преобразования, но я не вижу другого простого способа сделать это...

Ответ 3

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

Как сказал Тимви, вам также понадобятся некоторые встроенные правила, но они могут быть жестко закодированы.

Ответ 4

То, что вы ищете, - это наличие неявного перевода из одного типа в другой. Я бы подумал, что это можно сделать с помощью рефлексии, хотя это может быть сложно, потому что неявное литье должно быть определено как перегрузка оператора, которая является статическим методом, и я думаю, что она может быть определена в любом классе, а не только в том, что может быть неявно преобразовано.

Ответ 5

Ответ Timwi действительно завершен, но я чувствую, что существует даже более простой способ, который предоставит вам ту же семантику (проверьте "реальную" назначаемость), фактически не определяя себя что это.

Вы можете просто попробовать задание и искать InvalidCastException (я знаю, это очевидно). Таким образом, вы избегаете хлопот проверки трех возможных значений уступчивости, о которых упоминал Тимви. Здесь образец с использованием xUnit:

[Fact]
public void DecimalsShouldReallyBeAssignableFromInts()
{
    var d = default(decimal);
    var i = default(i);

    Assert.Throws<InvalidCastException)( () => (int)d);
    Assert.DoesNotThrow( () => (decimal)i);
}

Ответ 6

На самом деле бывает, что тип 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-интерфейсов, но у меня создается впечатление, что не будет служить вам, а также программировать для определенных нескольких случаев, о которых вы знаете.

Удачи!