Можно использовать тип Nullable как общий параметр?
Я хочу сделать что-то вроде этого:
myYear = record.GetValueOrNull<int?>("myYear"),
Обратите внимание на тип с нулевым значением в качестве общего параметра.
Так как функция 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;
}
Но ошибка, которую я получаю сейчас, это:
Тип 'int?' должен быть ссылочным типом, чтобы использовать его как параметр 'T' в родовом типе или методе
Right! 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 как общий параметр на всех возможных?
Ответы
Ответ 1
Измените тип возвращаемого значения на 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;
}
Ответ 2
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);
Ответ 3
Просто сделайте две вещи в своем исходном коде - удалите ограничение where
и измените последний return
с return null
на return default(T)
. Таким образом, вы можете вернуть любой тип, который вам нужен.
Кстати, вы можете избежать использования is
, изменив инструкцию if
на if (columnValue != DBNull.Value)
.
Ответ 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;
}
Ответ 5
Отказ от ответственности: Этот ответ работает, но предназначен только для образовательных целей.:) Решение Джеймса Джонса, вероятно, лучше всего здесь и, конечно же, того, с которым я бы пошел.
Ключевое слово С# 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 coder.
Ответ 6
Я думаю, что вы хотите обрабатывать типы ссылок и типы структуры.
Я использую его для преобразования строк XML Element в более типизированный тип.
Вы можете удалить nullAlternative с отражением.
Форматпровайдер предназначен для обработки зависимых от культуры ".". или ',' разделитель, например. десятичные знаки или ints и удваивает.
Это может работать:
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);
Ответ 7
Это может быть мертвая нить, но я склонен использовать следующее:
public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct
{
return reader[columnName] as T?;
}
Ответ 8
Я знаю, что это старо, но вот еще одно решение:
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);
Ответ 9
Я сам столкнулся с той же проблемой.
... = reader["myYear"] as int?;
работает и чист.
Он работает с любым типом без проблем. Если результатом является DBNull, он возвращает значение null при неудачном преобразовании.
Ответ 10
Несколько общих ограничений нельзя комбинировать по модулю OR (менее ограничительные), только в моделях AND (более ограничительные). Это означает, что один метод не может обрабатывать оба сценария. Общие ограничения также не могут использоваться для создания уникальной сигнатуры для метода, поэтому вам придется использовать 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;
}