Обнуляемый тип как универсальный параметр возможно? - PullRequest
251 голосов
/ 16 октября 2008

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

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 в качестве универсального параметра?

Ответы [ 10 ]

236 голосов
/ 16 октября 2008

Измените тип возвращаемого значения на 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;
}
101 голосов
/ 01 апреля 2011
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);
54 голосов
/ 17 октября 2008

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

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

5 голосов
/ 19 февраля 2012

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

Ключевое слово

C # 4.0 dynamic делает это еще проще, хотя и менее безопасным:

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.

4 голосов
/ 30 июля 2010

Просто нужно было сделать что-то невероятное похожее на это. Мой код:

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 голосов
/ 27 сентября 2011

Я думаю, что вы хотите обрабатывать ссылочные типы и структурные типы. Я использую его для преобразования строк 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 голосов
/ 06 ноября 2013

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

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
1 голос
/ 11 января 2017

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

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

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

1 голос
/ 28 августа 2014

Я знаю, что это старый, но вот другое решение:

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

0 голосов
/ 29 апреля 2017

Множественные общие ограничения не могут быть объединены способом ИЛИ (менее ограничительным), только способом И (более ограничительным). Это означает, что один метод не может обрабатывать оба сценария. Общие ограничения также нельзя использовать для создания уникальной подписи метода, поэтому вам придется использовать 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;
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...