Обнуляемый тип как универсальный параметр возможно?
Я хочу сделать что-то вроде этого:
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 в качестве универсального параметра?
13 ответов
Измените тип возвращаемого значения на 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;
}
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);
Просто сделайте две вещи со своим исходным кодом - удалите where
ограничение и изменить последний return
от return null
в return default(T)
, Таким образом, вы можете вернуть любой тип, который вы хотите.
Кстати, вы можете избежать использования is
изменив свой if
заявление к if (columnValue != DBNull.Value)
,
Множественные общие ограничения не могут быть объединены способом ИЛИ (менее ограничительным), только способом И (более ограничительным). Это означает, что один метод не может обрабатывать оба сценария. Общие ограничения также нельзя использовать для создания уникальной подписи для метода, поэтому вам придется использовать 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;
}
Отказ от ответственности: Этот ответ работает, но предназначен только для образовательных целей.:) Решение Джеймса Джонса, вероятно, лучшее здесь, и, конечно, я бы выбрал его.
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 coder.
Я думаю, что вы хотите обрабатывать ссылочные типы и структурные типы. Я использую его для преобразования строк 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);
Более короткий путь:
public static T ValueOrDefault<T>(this DataRow reader, string columnName) =>
reader.IsNull(columnName) ? default : (T) reader[columnName];
возвращение 0
за int
, а также null
за int?
Просто нужно было сделать что-то невероятное похожее на это. Мой код:
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 bool HasValueAndIsNotDefault<T>(this T? v)
where T : struct
{
return v.HasValue && !v.Value.Equals(default(T));
}
Это может быть мертвая тема, но я склонен использовать следующее:
public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct
{
return reader[columnName] as T?;
}
Я только что столкнулся с той же самой проблемой.
... = reader["myYear"] as int?;
работает и чисто.
Работает с любым типом без проблем. Если результат DBNull, он возвращает ноль, так как преобразование не выполняется.
Я знаю, что это старый, но вот другое решение:
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);
Вот метод расширения, который я использовал в течение многих лет:
public static T GetValue<T>(this DbDataReader reader, string columnName)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (string.IsNullOrWhiteSpace(columnName))
throw new ArgumentException("Value cannot be null or whitespace.", nameof(columnName));
// do not swallow exceptions here - let them bubble up to the calling API to be handled and/or logged
var index = reader.GetOrdinal(columnName);
if (!reader.IsDBNull(index))
{
return (T)reader.GetValue(index);
}
return default;
}