Неможливий тип як загальний параметр?


288

Я хочу зробити щось подібне:

myYear = record.GetValueOrNull<int?>("myYear"),

Зауважте нульовий тип як загальний параметр.

Оскільки GetValueOrNullфункція могла повернутись до нуля, моя перша спроба:

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : class
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
    {
        return (T)columnValue;
    }
    return null;
}

Але помилка, яку я отримую зараз, це:

Тип "int?" повинен бути посилальним типом, щоб використовувати його як параметр 'T' у загальному типі чи методі

Правильно! Nullable<int>є struct! Тому я спробував змінити обмеження класу на structобмеження (і як побічний ефект вже не можна повернутись null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

Тепер завдання:

myYear = record.GetValueOrNull<int?>("myYear");

Подає наступну помилку:

Тип "int?" повинен бути ненульовим типом значення, щоб використовувати його як параметр 'T' у загальному типі чи методі

Чи взагалі можливе визначення нульового типу як загального параметра?


3
Pls pls зробіть свій підпис IDataRecordвід DbDataRecord..
nawfal

Відповіді:


262

Змініть тип повернення на Nullable і викличте метод із параметром non nulable

static void Main(string[] args)
{
    int? i = GetValueOrNull<int>(null, string.Empty);
}


public static Nullable<T> GetValueOrNull<T>(DbDataRecord reader, string columnName) where T : struct
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
        return (T)columnValue;

    return null;
}

1
Я пропоную вам використовувати "columnValue == DBNull.Value" замість оператора "є", оскільки його трохи швидше =)
driAn

40
Особисті переваги, але ви можете використовувати коротку форму T? замість Nullable <T>
Dunc

11
Це добре для типів значень, але тоді я думаю, що він взагалі не буде працювати з посилальними типами (наприклад, GetValueOrNull <string>), оскільки C #, схоже, не любить Nullable <(ref type)> як "string?". Рішення Роберта К. Барта та Джеймса Джонса здаються мені набагато кращими, якщо це ваша потреба.
бакар

2
@bacar - правильно, отже, "де T: struct", якщо ви хочете посилальних типів, ви можете створити подібний метод з "де T: клас"
Грег Дін

4
@Greg - звичайно, але тоді вам потрібен другий метод, і ви не можете перевантажувати ім’я. Як я кажу, якщо ви хочете працювати з типами val і ref, я думаю, що на цій сторінці представлені більш чисті рішення.
бакар

107
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

    if (!(val is DBNull))
        return (T)val;

    return default(T);
}

Просто використовуйте його так:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);

6
Це може бути скорочено до: return rdr.IsDBNull (index)? за замовчуванням (T): (T) rdr [індекс];
Foole

11
Я думаю, що це питання явно хоче нульовий , а не за замовчуванням (T) .
мафу

5
За замовчуванням @mafu (T) поверне нуль для типових типів, а 0 для числових типів, зробивши рішення більш гнучким.
Джеймс Джонс

2
Я думаю, що зрозуміліше або зателефонувати цьому, GetValueOrDefaultщоб уточнити, що воно повертається, default(T)а не null. Крім того, ви можете викинути виняток, якщо Tвін не зводиться до нуля.
Сем

Цей метод має безліч переваг і змушує задуматися про повернення чогось іншого, крім нульового.
Шейн

61

Просто виконайте дві речі з початковим кодом - видаліть whereобмеження та змініть останнє returnз return nullна return default(T). Таким чином ви можете повернути будь-який тип.

До речі, ви можете уникнути використання is, змінивши свою ifзаяву на if (columnValue != DBNull.Value).


4
Це рішення не працює, оскільки існує логічна різниця між NULL та 0
Грег Дін

15
Він працює, якщо тип, який він передає, int ?. Він поверне НУЛЬ так, як він хоче. Якщо він передасть int як тип, він поверне 0, оскільки int не може бути NULL. Крім того, що я спробував це, він справно працює.
Роберт К. Барт

2
Це найбільш правильна і гнучка відповідь. Однак return defaultдостатньо ( (T)компілятор не потребує цього , компілятор виводить його з типу повернення підпису).
McGuireV10

5

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

dynamicКлючове слово C # 4.0 робить це ще простіше, якщо менш безпечно:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

Тепер вам не потрібно явного натяку на тип RHS:

int? value = myDataReader.GetNullableValue("MyColumnName");

Насправді вам це зовсім не потрібно!

var value = myDataReader.GetNullableValue("MyColumnName");

value тепер буде int, рядок або будь-який тип, який було повернуто з БД.

Єдина проблема полягає в тому, що це не заважає вам використовувати ненульові типи на LHS, і в цьому випадку ви отримаєте досить неприємний виняток під час виконання, наприклад:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

Як і з усім кодом, який використовує dynamic: застереження кодера.


4

Просто треба було зробити щось неймовірне подібне до цього. Мій код:

public T IsNull<T>(this object value, T nullAlterative)
{
    if(value != DBNull.Value)
    {
        Type type = typeof(T);
        if (type.IsGenericType && 
            type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return (T)(type.IsEnum ? Enum.ToObject(type, Convert.ToInt32(value)) :
            Convert.ChangeType(value, type));
    }
    else 
        return nullAlternative;
}

3

Я думаю, ви хочете опрацювати довідкові типи та типи структур. Я використовую його для перетворення рядків XML Element у більш набраний тип. Ви можете видалити nullAlternative за допомогою відображення. Форматпровідник повинен обробляти залежність від культури "." або "," роздільник, наприклад, десяткових знаків, літер та пар. Це може працювати:

public T GetValueOrNull<T>(string strElementNameToSearchFor, IFormatProvider provider = null ) 
    {
        IFormatProvider theProvider = provider == null ? Provider : provider;
        XElement elm = GetUniqueXElement(strElementNameToSearchFor);

        if (elm == null)
        {
            object o =  Activator.CreateInstance(typeof(T));
            return (T)o; 
        }
        else
        {
            try
            {
                Type type = typeof(T);
                if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                {
                    type = Nullable.GetUnderlyingType(type);
                }
                return (T)Convert.ChangeType(elm.Value, type, theProvider); 
            }
            catch (Exception)
            {
                object o = Activator.CreateInstance(typeof(T));
                return (T)o; 
            }
        }
    }

Ви можете використовувати його так:

iRes = helper.GetValueOrNull<int?>("top_overrun_length");
Assert.AreEqual(100, iRes);



decimal? dRes = helper.GetValueOrNull<decimal?>("top_overrun_bend_degrees");
Assert.AreEqual(new Decimal(10.1), dRes);

String strRes = helper.GetValueOrNull<String>("top_overrun_bend_degrees");
Assert.AreEqual("10.1", strRes);

2

Це може бути глуха нитка, але я схильний використовувати таке:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}

1
"Тип" T "повинен бути ненульовим типом значення, щоб використовувати його як параметр" T "в загальному типі або методі" Nullable <T> ""
Ian Warburton

1

Я просто зіткнувся з тією ж проблемою сам.

... = reader["myYear"] as int?; працює і чисто.

Він працює з будь-яким типом без проблем. Якщо результат DBNull, він повертає null, оскільки перетворення не вдається.


Насправді ви, ймовірно, можете зробити int v=reader["myYear"]??-1;чи якийсь дефолт замість цього -1. Однак це може спричинити проблеми, якщо значення буде DBNull...
nurchi

1

Я знаю, що це старе, але ось інше рішення:

public static bool GetValueOrDefault<T>(this SqlDataReader Reader, string ColumnName, out T Result)
{
    try
    {
        object ColumnValue = Reader[ColumnName];

        Result = (ColumnValue!=null && ColumnValue != DBNull.Value) ? (T)ColumnValue : default(T);

        return ColumnValue!=null && ColumnValue != DBNull.Value;
    }
    catch
    {
        // Possibly an invalid cast?
        return false;
    }
}

Тепер вам байдуже, чи Tбуло це значення або тип посилання. Тільки якщо функція повертає true, ви маєте розумне значення з бази даних. Використання:

...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

Цей підхід дуже схожий на int.TryParse("123", out MyInt);


Було б добре, якби ви працювали над своїми умовами іменування. Їм не вистачає послідовності. В одному місці є змінна без капіталу, тоді є одна з. Те саме з параметрами методів.
Marino Šimić

1
Готово і готово! Код надії зараз виглядає краще. Боб - твоя тітка :) Все є skookum
nurchi

0

Кілька загальних обмежень не можна поєднувати в АБО (менш обмежувальний), лише в І-І (більш обмежувальний). Це означає, що один метод не може впоратися з обома сценаріями. Загальні обмеження також не можна використовувати для створення унікальної сигнатури методу, тому вам доведеться використовувати 2 окремих імена методу.

Однак ви можете використовувати загальні обмеження, щоб переконатися, що методи використовуються правильно.

У моєму випадку я спеціально хотів, щоб нульове значення було повернене, а ніколи не було значенням за замовчуванням будь-яких можливих типів значень. GetValueOrDefault = погано. GetValueOrNull = добре.

Я використовував слова "Null" та "Nullable", щоб розрізняти типи посилань та типи значень. Ось приклад декількох методів розширення, про які я писав, що компліментує метод FirstOrDefault у класі System.Linq.Enumerable.

    public static TSource FirstOrNull<TSource>(this IEnumerable<TSource> source)
        where TSource: class
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a class is null
        return result;
    }

    public static TSource? FirstOrNullable<TSource>(this IEnumerable<TSource?> source)
        where TSource : struct
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a nullable is null
        return result;
    }

0

Коротший шлях:

public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

повернення 0за int, і nullзаint?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.