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
Я особисто хотів би знати, що ви робите спеціально для кожного типу даних. Якщо ви виконуєте приблизно одне і те ж перетворення для кожного типу даних, можливо, буде простіше зіставити різні типи до загального інтерфейсу та працювати з цим інтерфейсом.
Джульєтта

Відповіді:


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: перевантаження тут, безумовно, є найкращим рішенням з точки зору дизайну та довгострокової ремонтопридатності. Перевірка типу виконання загального параметра просто здається занадто іронічною для кодування з прямою обличчям.
Джульєтта

Прекрасним прикладом того, коли це було б корисно, є загальна серіалізація з дико різними типами. Якщо об'єкт, що передається, є рядком, чому зайва робота? Якщо це рядок, просто поверніть початковий рядок, не намагаючись зайвої обробки
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))
    ...
}

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)

... не буде працювати з оператором перемикача 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 повинен забезпечуватися інтегральними типами, які не включають складні типи, такі як об'єкт "Type" або будь-який інший тип об'єкта.


2

Ваша конструкція повністю перешкоджає призначенню загального методу. Це навмисно потворно, бо повинен бути кращий спосіб досягти того, що ви намагаєтесь досягти, хоча ви не дали нам достатньо інформації, щоб зрозуміти, що це таке.


2

Ви можете зробити typeOf(T) , але я б двічі перевірив ваш метод і переконався, що ви не порушуєте тут жодної відповідальності. Це був би запах коду, і це не означає, що цього не слід робити, але що ви повинні бути обережними.

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



0

Як щодо цього:

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.