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





Измените тип возвращаемого значения на 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', потому что он немного быстрее =)
Личные предпочтения, но вы можете использовать сокращенную форму T? вместо Nullable <T>
Это нормально для типов значений, но я думаю, что это вообще не будет работать со ссылочными типами (например, GetValueOrNull <string>), потому что C#, похоже, не нравится Nullable <(ref type)> как «строка?». Приведенные ниже решения Роберта Барта и Джеймса Джонса кажутся мне намного лучше, если вам это нужно.
@bacar - правильно, отсюда "where T: struct", если вам нужны ссылочные типы, вы можете создать аналогичный метод с "where T: class"
@Greg - конечно, но тогда нужен второй метод, и перегрузить имя нельзя. Как я сказал, если вы хотите обрабатывать как типы val, так и ref, я думаю, что более чистые решения представлены на этой странице.
В .NET 4 я смог использовать T? вместо Nullable<T>, это немного чище.
@bacar Для этого есть веская причина - nullable допускает "sort-of-null", но он по-прежнему имеет семантику значений, а не семантику ссылок. Nullable`1 - это особый тип с точки зрения среды выполнения, в котором задействовано много "магии".
Специально не запрашивается в вопросе, но это лучший вариант, если вы хотите, чтобы ваш метод принимал параметр T, но должен возвращать форму T?, допускающую значение NULL. Однако у него есть ограничения, о которых упоминает @bacar.
Просто сделайте две вещи с исходным кодом - удалите ограничение where и измените последний return с return null на return default(T). Таким образом, вы можете вернуть любой желаемый тип.
Кстати, вы можете избежать использования is, изменив оператор if на if (columnValue != DBNull.Value).
Это решение не работает, поскольку существует логическая разница между NULL и 0
Он работает, если он передает тип int ?. Он вернет NULL, как он и хочет. Если он передает int в качестве типа, он вернет 0, поскольку int не может быть NULL. Помимо того, что я попробовал, и он отлично работает.
Это наиболее правильный и гибкий ответ. Однако return default достаточно ((T) вам не нужен, компилятор определит его из типа возвращаемого значения сигнатуры).
Просто пришлось сделать что-то невероятное, подобное этому. Мой код:
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 [индекс];
Я думаю, что этот вопрос явно хочет нулевой, а не по умолчанию (T).
@mafu default (T) вернет null для ссылочных типов и 0 для числовых типов, что сделает решение более гибким.
Я думаю, что будет понятнее назвать это GetValueOrDefault, чтобы уточнить, что он возвращает default(T), а не null. В качестве альтернативы вы можете создать исключение, если T не допускает значения NULL.
У этого метода много преимуществ, и он заставляет задуматься о возврате чего-то другого, кроме null.
@mafu для типов значений, вы можете явно указать, что ищете нули: GetValueOrDefault <decimal?>
Я думаю, вы хотите обрабатывать ссылочные типы и типы структур. Я использую его для преобразования строк элемента 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>'»
Я знаю, что это устарело, но вот еще одно решение:
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);.
Было бы хорошо, если бы вы работали над своими соглашениями об именах. Им не хватает последовательности. В одном месте есть переменная без заглавной буквы, тогда есть переменная с. То же и с параметрами к методам.
Готово и готово! Надеюсь, теперь код выглядит лучше. Боб твоя тетя :) Все скукум
Я сам столкнулся с той же проблемой.
... = reader["myYear"] as int?; работает и чисто.
Он работает с любым типом без проблем. Если результатом является DBNull, он возвращает null, поскольку преобразование завершается ошибкой.
Фактически, вы, вероятно, могли бы использовать int v=reader["myYear"]??-1; или какой-либо другой по умолчанию вместо -1. Однако это может вызвать проблемы, если значение DBNull ...
Множественные общие ограничения не могут быть объединены ИЛИ (менее строго), только И (более строго). Это означает, что один метод не может обрабатывать оба сценария. Общие ограничения также нельзя использовать для создания уникальной подписи для метода, поэтому вам придется использовать два отдельных имени метода.
Однако вы можете использовать общие ограничения, чтобы убедиться, что методы используются правильно.
В моем случае я специально хотел, чтобы возвращался 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));
}
Пожалуйста, сделайте свою подпись
IDataRecordотDbDataRecord..