Есть ли способ намека на C #, как выполнить вывод общего типа? - PullRequest
3 голосов
/ 06 июня 2019

Я пытаюсь добавить проверки времени компиляции в код, который ранее принимал строковое имя свойства объекта данных и значение типа «объект». Я делаю это для того, чтобы убедиться, что свойство и значение на самом деле относятся к одному и тому же типу, чтобы предотвратить случаи, когда они не представляют ошибку времени выполнения.

Я обрабатываю проверку времени компиляции, создав метод, который принимает выражение типа Expression<Func<TDataObject, TPropertyValue>> и параметр типа TPropertyValue. Из этого я могу проверить выражение, чтобы получить имя возвращаемого свойства, а затем использовать ту же логику, что и сегодня, со строками и значением в качестве типа объекта.

public interface IPropertyDictionary<TDataObject> where TDataObject : class
{
    void AddIfSameType<TProperty>(
        Expression<Func<TDataObject, TProperty>> propertyAccessExpression,
        TProperty propertyValue);
}

Следующее работает как ожидалось:

// Allowed
propDictionary.AddIfSameType(e => e.IntProperty, 123);

// Flagged by intellisense (though the expression is flagged rather than second parameter...)
propDictionary.AddIfSameType(e => e.IntProperty, "asdf");

Однако это не работает, как ожидалось:

// Not flagged as error
propDictionary.AddIfSameType(e => e.IntProperty, 123L);

При этом C # выводит TPropertyValue в значение long , а не в int. В отладчике я вижу, что выражение преобразуется, чтобы привести его к длинному:

e => Convert(e.IntProperty)

В моем идеальном случае, C # предпочел бы тип IntProperty, когда делает вывод типа, и выдает ошибку времени компиляции, указывающую, что приведение от long к int требует явного преобразования. Есть ли способ указать C #, что он должен использовать только первый параметр метода при выводе типа? Единственная альтернатива, которая у меня есть на данный момент, это явно указать параметр типа:

// Flagged by intellisense
propDictionary.AddIfSameType<int>(e => e.IntProperty, 123L);

Но в 99% случаев люди не будут передавать параметр типа, и я не ожидал бы, что они поймут, что они должны делать это в этой ситуации. В результате их ошибка снова становится одной из ошибок времени выполнения, которых я очень хочу избежать.

Ответы [ 3 ]

6 голосов
/ 06 июня 2019

Да, немного окольным путем. Используйте два параметра типа и ограничение параметра типа . Ограничения равенства не существует, но ограничение наследования должно работать для большинства сценариев:

static void AddIfSameType<TLProp,TRProp>(Func<DataObject,TLProp> lprop, TRProp rprop) where TRProp : TLProp
{

}
static void Main(string[] args)
{
    AddIfSameType(d => d.IntProperty, 1);
    //compiles

    AddIfSameType(d => d.IntProperty, 1L); 
    //Error CS0315  The type 'long' cannot be used as type parameter 'TRProp' 
    //... There is no boxing conversion from 'long' to 'int'.
}
4 голосов
/ 06 июня 2019

Невозможно сделать то, что вы описываете, при сохранении точного синтаксиса, который вы запрашиваете. (Я исправлен: см. Ответ Дэвида Брауна, чтобы узнать, как это сделать.)

Если вы хотите изменить свой подход, вы можете отделить вызов метода с выражением от вызова со значением.Примерно так:

propDictionary.AdderFor(e => e.IntProperty).Add(123L);

Одним из потенциальных преимуществ этого подхода является то, что вы можете захватить «сумматор» как переменную.

var intAdder = propDictionary.AdderFor(e => e.IntProperty)
intAdder.Add(456);  // okay
intAdder.Add(123L); // error
0 голосов
/ 06 июня 2019

Как C # должен знать, что TDataObject имеет свойство IntProperty? Все, что вы говорите, это то, что TDataObject должно быть class с where TDataObject : class. Вы должны указать ограничение, которое позволяет C # знать об этом свойстве (и, возможно, других). Э.Г.

public interface IProperties
{
    int IntProperty { get; set; }
    double DoubleProperty { get; set; }
    string StringProperty { get; set; }
}

public interface IPropertyDictionary<TDataObject> where TDataObject : IProperties
{
    void AddIfSameType<TProperty>(
        Expression<Func<TDataObject, TProperty>> getProp, TProperty value);
}

Тогда вы можете объявить словарь с помощью

public class PropDictionary<TDataObject> : IPropertyDictionary<TDataObject>
    where TDataObject : IProperties
{
    public void AddIfSameType<TProperty>(
        Expression<Func<TDataObject, TProperty>> getProp, TProperty value)
    {
    }
}

и класс данных

public class DataObject : IProperties
{
    public int IntProperty { get; set; }
    public double DoubleProperty { get; set; }
    public string StringProperty { get; set; }
}

Теперь оба эти вызова работают

var propDictionary = new PropDictionary<DataObject>();

propDictionary.AddIfSameType(e => e.DoubleProperty, 123);
propDictionary.AddIfSameType(e => e.IntProperty, 123L);

Почему они работают?

Первый, потому что типы выводятся как

void ProperDictionary<DataObject>.AddIfSameType<double>(
    Expression<Func<DataObject, double>> getProp, double value)

Переданное значение int просто приведено к double.

Второй случай немного удивителен. Типы определены как

void ProperDictionary<DataObject>.AddIfSameType<long>(
    Expression<Func<DataObject, long>> getProp, longvalue)

Я предполагаю, что возвращаемое значение неявно расширено:

e => (long)e.IntProperty

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

UPDATE

Поэтому используйте два различных параметра типа для типа возвращаемого значения Func и значения.

public void AddIfSameType<TProp, TValue>(
    Expression<Func<TDataObject, TProp>> getProp, TValue value)
{
    if (typeof(TProp) == typeof(TValue)) {

    } else {

    }
}

C # выведет точный тип для каждого из этих параметров типа. Вместо этого вы также можете ввести значение object, но с учетом стоимости типов значений.

...