Возможен ли тип, допускающий значение NULL, в качестве универсального параметра?

Я хочу сделать что-то вроде этого:

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

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

Поскольку функция GetValueOrNull могла вернуть значение null, моя первая попытка была такой:

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;
}

Но теперь я получаю следующую ошибку:

The type 'int?' must be a reference type in order to use it as parameter 'T' in the generic type or method

Верно! Nullable<int> - это struct! Поэтому я попытался изменить ограничение класса на ограничение struct (и, как побочный эффект, больше не может возвращать null):

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

Теперь задание:

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

Выдает следующую ошибку:

The type 'int?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method

Возможно ли вообще указать тип, допускающий значение NULL, в качестве универсального параметра?

Пожалуйста, сделайте свою подпись IDataRecord от DbDataRecord ..

nawfal 07.02.2013 09:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
317
1
231 309
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

Измените тип возвращаемого значения на Nullable<T> и вызовите метод с параметром, не допускающим значения NULL.

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;
}

Я предлагаю вам использовать "columnValue == DBNull.Value" вместо оператора 'is', потому что он немного быстрее =)

driAn 27.03.2009 00:33

Личные предпочтения, но вы можете использовать сокращенную форму T? вместо Nullable <T>

Dunc 09.09.2010 19:04

Это нормально для типов значений, но я думаю, что это вообще не будет работать со ссылочными типами (например, GetValueOrNull <string>), потому что C#, похоже, не нравится Nullable <(ref type)> как «строка?». Приведенные ниже решения Роберта Барта и Джеймса Джонса кажутся мне намного лучше, если вам это нужно.

bacar 28.07.2011 14:43

@bacar - правильно, отсюда "where T: struct", если вам нужны ссылочные типы, вы можете создать аналогичный метод с "where T: class"

Greg Dean 16.08.2011 03:30

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

bacar 17.08.2011 11:55

В .NET 4 я смог использовать T? вместо Nullable<T>, это немного чище.

craastad 01.04.2013 18:36

@bacar Для этого есть веская причина - nullable допускает "sort-of-null", но он по-прежнему имеет семантику значений, а не семантику ссылок. Nullable`1 - это особый тип с точки зрения среды выполнения, в котором задействовано много "магии".

Luaan 15.07.2016 16:10

Специально не запрашивается в вопросе, но это лучший вариант, если вы хотите, чтобы ваш метод принимал параметр T, но должен возвращать форму T?, допускающую значение NULL. Однако у него есть ограничения, о которых упоминает @bacar.

Per Lundberg 01.02.2020 09:48

Просто сделайте две вещи с исходным кодом - удалите ограничение where и измените последний return с return null на return default(T). Таким образом, вы можете вернуть любой желаемый тип.

Кстати, вы можете избежать использования is, изменив оператор if на if (columnValue != DBNull.Value).

Это решение не работает, поскольку существует логическая разница между NULL и 0

Greg Dean 17.10.2008 15:53

Он работает, если он передает тип int ?. Он вернет NULL, как он и хочет. Если он передает int в качестве типа, он вернет 0, поскольку int не может быть NULL. Помимо того, что я попробовал, и он отлично работает.

Robert C. Barth 23.10.2008 04:20

Это наиболее правильный и гибкий ответ. Однако return default достаточно ((T) вам не нужен, компилятор определит его из типа возвращаемого значения сигнатуры).

McGuireV10 19.01.2018 15:58

Просто пришлось сделать что-то невероятное, подобное этому. Мой код:

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;
}

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);

Это можно было бы сократить до: return rdr.IsDBNull (index)? по умолчанию (T): (T) rdr [индекс];

Foole 09.11.2011 08:33

Я думаю, что этот вопрос явно хочет нулевой, а не по умолчанию (T).

mafu 16.02.2014 22:24

@mafu default (T) вернет null для ссылочных типов и 0 для числовых типов, что сделает решение более гибким.

James Jones 15.03.2014 23:52

Я думаю, что будет понятнее назвать это GetValueOrDefault, чтобы уточнить, что он возвращает default(T), а не null. В качестве альтернативы вы можете создать исключение, если T не допускает значения NULL.

Sam 14.07.2014 04:36

У этого метода много преимуществ, и он заставляет задуматься о возврате чего-то другого, кроме null.

Shane 25.06.2018 20:45

@mafu для типов значений, вы можете явно указать, что ищете нули: GetValueOrDefault <decimal?>

avs099 25.07.2018 16:41

Я думаю, вы хотите обрабатывать ссылочные типы и типы структур. Я использую его для преобразования строк элемента XML в более типизированный тип. Вы можете удалить nullAlternative с помощью отражения. Formatprovider должен обрабатывать зависящий от языка и региональных параметров '.' или разделитель ',', например, десятичные или целые и двойные. Это может сработать:

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);

Заявление об ограничении ответственности: Этот ответ работает, но предназначен только для образовательных целей. :) Решение Джеймса Джонса, вероятно, здесь лучший, и я бы выбрал его.

Ключевое слово 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 типы, не допускающие значения NULL, и в этом случае вы получите довольно неприятное исключение времени выполнения, например:

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

Как и весь код, использующий dynamic: caveat coder.

Это может быть мертвый поток, но я обычно использую следующее:

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

«Тип 'T' должен быть типом значения, не допускающим значения NULL, чтобы использовать его в качестве параметра 'T' в универсальном типе или методе 'Nullable <T>'»

Ian Warburton 08.03.2017 03:34

Я знаю, что это устарело, но вот еще одно решение:

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 значением или ссылочным типом. Только если функция возвращает истину, у вас есть разумное значение из базы данных. Использование:

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

Этот подход очень похож на int.TryParse("123", out MyInt);.

Было бы хорошо, если бы вы работали над своими соглашениями об именах. Им не хватает последовательности. В одном месте есть переменная без заглавной буквы, тогда есть переменная с. То же и с параметрами к методам.

Marino Šimić 25.11.2017 07:38

Готово и готово! Надеюсь, теперь код выглядит лучше. Боб твоя тетя :) Все скукум

nurchi 27.11.2017 20:40

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

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

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

Фактически, вы, вероятно, могли бы использовать int v=reader["myYear"]??-1; или какой-либо другой по умолчанию вместо -1. Однако это может вызвать проблемы, если значение DBNull ...

nurchi 27.11.2017 20:43

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

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

В моем случае я специально хотел, чтобы возвращался 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;
    }

Более короткий путь:

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

вернуть 0 для int и null для int?

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

public static bool HasValueAndIsNotDefault<T>(this T? v)
    where T : struct
{
    return v.HasValue && !v.Value.Equals(default(T));
}

Другие вопросы по теме