Обобщения C # и проверка типов

83

У меня есть метод, который использует IList<T>в качестве параметра. Мне нужно проверить, что это за тип Tобъекта, и что-то сделать на его основе. Я пытался использовать Tзначение, но компилятор не позволяет этого. Мое решение следующее:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

Должен быть способ сделать это лучше. Есть ли способ проверить тип Tпереданного, а затем использовать switchоператор?

Джон
источник
1
Я лично хотел бы знать, что вы делаете особенного для каждого типа данных. Если вы выполняете примерно одно и то же преобразование для каждого типа данных, может быть проще сопоставить разные типы с общим интерфейсом и работать с этим интерфейсом.
Juliet

Ответы:

121

Вы можете использовать перегрузки:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

Или вы можете проверить тип универсального параметра:

Type listType = typeof(T);
if(listType == typeof(int)){...}
Джонни
источник
23
+1: перегрузки - определенно лучшее решение с точки зрения дизайна и долговременной ремонтопригодности. Проверка типа универсального параметра во время выполнения кажется слишком ироничной для кодирования с серьезным лицом.
Juliet
Отличным примером того, когда это может быть полезно, является универсальная сериализация с очень разными типами. Если переданный объект является строкой, зачем нужна дополнительная работа? Если это строка, просто верните исходную строку без дополнительной обработки
watkinsmatthewp
Извините, есть ли способ добиться этого, используя switch-caseвместо if-else?
Tân
@HappyCoding, к сожалению, нет = (возможно, вы сможете сделать это в следующей версии C #.
jonnii
7
Вы НЕ должны полагаться на общую перегрузку (см. Этот ответ ), если ваши перегрузки функционально отличаются (также учитывайте побочные эффекты), потому что вы не можете гарантировать, что будет вызвана более специализированная перегрузка. Правило здесь следующее: если вы ДОЛЖНЫ выполнять специализированную логику для определенного типа, вы должны проверять этот тип и не использовать перегрузку; однако, если вы ПРЕДПОЧТИТЕЛЬНО выполняете только специализированную логику (например, для улучшения производительности), но все перегрузки, включая общий случай, приводят к тому же результату, тогда вы можете использовать перегрузку вместо проверки типов.
tomosius
23

Вы можете использовать typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}
Bdowden
источник
7

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

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

Полная запись в блоге и подробности о реализации доступны здесь.

ДжаредПар
источник
6

И поскольку C # эволюционировал, вы можете (теперь) использовать сопоставление с образцом .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

И снова с выражениями переключателя в C # 8.0 синтаксис становится еще более лаконичным.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}
Комплект
источник
4

Оператор typeof ...

typeof(T)

... не будет работать с оператором switch в C #. Но как насчет этого? Следующий пост содержит статический класс ...

Есть ли лучшая альтернатива «включить тип»?

... это позволит вам написать такой код:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
Роберт Харви
источник
Также см. Ответ JaredPar здесь.
Роберт Харви,
3

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

Например, если у вас есть класс, который, скажем, реализован таким образом (Примечание: я не показываю все, что делает этот код для простоты, а просто вырезал и вставил здесь, поэтому он может не строиться или работать так, как задумано, как весь код, но он передает суть. Кроме того, Unit - это перечисление):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

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

Джон
источник
2

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

мать
источник
2

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

mqp
источник
2

Ты можешь сделать typeOf(T) , но я бы дважды проверил ваш метод и убедился, что вы здесь не нарушаете единую ответственность. Это будет запах кода, и это не значит, что этого не следует делать, но вы должны быть осторожны.

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

Джош Берке
источник
2

Надеюсь, вы найдете это полезным:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

Джайдер
источник
0

Как насчет этого :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Берт
источник