Обнуляемый тип в качестве общего параметра возможно?

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' в универсальном типе или методе

Можно ли вообще указать тип NULL в качестве универсального параметра?

Том Пестер
источник
3
Просьба сделать вашу подпись IDataRecordот DbDataRecord..
Nawfal

Ответы:

262

Измените тип возвращаемого значения на Nullable и вызовите метод с ненулевым параметром

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» вместо оператора «is», потому что он немного быстрее =)
driAn
40
Личные предпочтения, но вы можете использовать краткую форму T? вместо Nullable <T>
Дунк
11
Это хорошо для типов значений, но тогда я думаю, что это не будет работать вообще со ссылочными типами (например, GetValueOrNull <string>), потому что C # не похоже на Nullable <(ref type)> как «string?». Решения Роберта С. Барта и Джеймса Джонса, представленные ниже, кажутся мне намного лучше, если это вам нужно.
Бакар
2
@bacar - верно, отсюда и «где T: структура», если вы хотите ссылочные типы, вы можете создать аналогичный метод с «где T: класс»
Грег Дин
4
@ Грег - конечно, но тогда вам нужен второй метод, и вы не можете перегрузить имя. Как я уже сказал, если вы хотите работать с типами 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 [индекс];
Фул
11
Я думаю, что этот вопрос явно хочет ноль , а не по умолчанию (T) .
Мафу
5
@mafu default (T) вернет null для ссылочных типов и 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 ?. Он вернет NULL, как он хочет. Если он передает 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: Caveat Coder.

Ян Кемп
источник
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-элемента в более типизированный тип. Вы можете удалить нуль-альтернативу с отражением. Формат провайдер должен обрабатывать зависимый от культуры "." или разделитель ',', например, в десятичных или целых числах и двойных числах. Это может работать:

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' должен быть типом значения, не допускающим значения NULL, чтобы использовать его в качестве параметра 'T' в универсальном типе или методе 'Nullable <T>'"
Ян
1

Я только что столкнулся с той же самой проблемой.

... = reader["myYear"] as int?; работает и чисто.

Работает с любым типом без проблем. Если результат DBNull, он возвращает ноль, так как преобразование не выполняется.

Хеле
источник
На самом деле, вы могли бы сделать 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);

nurchi
источник
Было бы хорошо, если бы вы работали над соглашениями об именах. Им не хватает последовательности. В одном месте есть переменная без заглавной буквы, тогда есть переменная с. То же самое с параметрами методов.
Марино Шимич
1
Сделано и сделано! Надеюсь, код теперь выглядит лучше. Боб твоя тетушка :) Все это
скукум
0

Множественные общие ограничения не могут быть объединены способом ИЛИ (менее ограничительным), только способом И (более ограничительным). Это означает, что один метод не может обрабатывать оба сценария. Общие ограничения также нельзя использовать для создания уникальной подписи для метода, поэтому вам придется использовать 2 отдельных имени метода.

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

В моем случае я специально хотел вернуть значение null, а не значение по умолчанию любых возможных типов значений. 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?

Амирхосейн Яри
источник