Может ли общий метод обрабатывать типы ссылок и нулевых значений?

У меня есть ряд методов расширения, чтобы помочь с проверкой нулей на объектах IDataRecord, которые я сейчас реализую следующим образом:

public static int? GetNullableInt32(this IDataRecord dr, int ordinal)
{
    int? nullInt = null;
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal);
}

public static int? GetNullableInt32(this IDataRecord dr, string fieldname)
{
    int ordinal = dr.GetOrdinal(fieldname);
    return dr.GetNullableInt32(ordinal);
}

и т.д. для каждого типа, с которым мне нужно иметь дело.

Я хотел бы переопределить их как общий метод, отчасти для уменьшения избыточности и отчасти для того, чтобы научиться писать общие методы в целом.

Я написал это:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal)
{
    Nullable<T> nullValue = null;
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal);
}

который работает до тех пор, пока T является типом значения, но если T является ссылочным типом, он не будет.

Этот метод должен будет возвращать либо тип Nullable, если T - тип значения, а default (T) в противном случае. Как реализовать это поведение?

Ответ 1

Вы можете просто объявить свой метод следующим образом:

public static T GetNullable<T>(this IDataRecord dr, int ordinal)
{
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal);
}

Таким образом, если T является нулевым int или любым другим значением типа nullable, он фактически возвращает null. Если это обычный тип данных, он просто вернет значение по умолчанию для этого типа.

Ответ 2

Это работает:

public static T Get<T>( this IDataRecord dr, int ordinal) 
{
    T  nullValue = default(T);
    return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal);
}


public void Code(params string[] args)
{
    IDataRecord dr= null;
    int? a = Get<int?>(dr, 1);
    string b = Get<string>(dr, 2);
}

Ответ 3

Я не думаю, что вы можете реализовать это с помощью одной функции. Если С# поддерживает перегрузку на основе типа возврата, возможно, вы сможете, но даже тогда я бы рекомендовал не делать этого.

Вы должны иметь возможность выполнить одно и то же, не используя типы с нулевыми типами данных и возвращая либо фактическое значение, либо null, как предложили BFree.

Ответ 4

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

Для типов значений, где null действителен, используйте int? iNullable = dr[ordinal] as int?;.

Для типов значений, где null является недопустимым, используйте int iNonNullable = dr[ordinal] as int? ?? default(int);.

Для ссылочных типов используйте string sValue = dr[ordinal] as string;.

Для тех, кто считает, что код не работает и что dr[ordinal] будет генерировать исключение для DBNull, приведен примерный метод, который когда-то дал правильную строку соединения, докажет концепцию.

private void Test()
{
  int? iTestA;
  int? iTestB;
  int iTestC;
  string sTestA;
  string sTestB;

  //Create connection
  using (SqlConnection oConnection = new SqlConnection(@""))
  {
    //Open connection
    oConnection.Open();

    //Create command
    using (SqlCommand oCommand = oConnection.CreateCommand())
    {
      //Set command text
      oCommand.CommandText = "SELECT null, 1, null, null, '1'";

      //Create reader
      using (SqlDataReader oReader = oCommand.ExecuteReader())
      {
        //Read the data
        oReader.Read();

        //Set the values
        iTestA = oReader[0] as int?;
        iTestB = oReader[1] as int?;
        iTestC = oReader[2] as int? ?? -1;
        sTestA = oReader[3] as string;
        sTestB = oReader[4] as string;
      }
    }
  }
}

Ответ 5

Вы не можете сделать это одним способом, но вы делаете это с тремя:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index)
{
    if (!reader.IsClosed)
    {
        return getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? (T?)null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

Затем, чтобы использовать его, вы должны:

private static Whatever CreateObject(IDataReader reader)
{
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0);
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1);
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2);
}

Ответ 6

public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal);
}

или более результативно

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal);
}

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i);
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i);
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i);

и используйте его так:

rec.Get(GetString, index);
rec.Get(GetInt32, index);

Ответ 7

Структура Nullable относится только к типам значений, потому что ссылочные типы могут быть равно NULL...

Ответ 8

Я делаю так:

DataRow record = GetSomeRecord();
int? someNumber = record[15] as int?
Guid? someUID = record["MyPrimaryKey"] as Guid?;
string someText = GetSomeText();
record["Description"] = someText.ToDbString();

// ........

public static class StringExtensionHelper {
    public static object ToDbString( this string text ) {
         object ret = null != text ? text : DBNull.Value
         return ret;
    }
}

EDIT: Вы можете (или вы должны) использовать методы расширения ToDbInt32, ToDbBool и т.д. Для других примитивных типов.

ИЗМЕНИТЬ 2: Вы также можете расширить базовый класс "объект" с помощью "ToDbValue".

public static class StringExtensionHelper {
    public static object ToDbValue( this object value ) {
         object ret = object.ReferenceEquals( value, null ) ? (object)DBNull.Value : value;
         return ret;
    }
}