Jenis nullable sebagai parameter generik mungkin?

287

Saya ingin melakukan sesuatu seperti ini:

myYear = record.GetValueOrNull<int?>("myYear"),

Perhatikan tipe nullable sebagai parameter generik.

Karena GetValueOrNullfungsi tersebut dapat mengembalikan nol, upaya pertama saya adalah ini:

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

Tapi kesalahan yang saya dapatkan sekarang adalah:

Jenis 'int?' harus menjadi tipe referensi untuk menggunakannya sebagai parameter 'T' dalam tipe atau metode umum

Baik! Nullable<int>adalah struct! Jadi saya mencoba mengubah batasan kelas menjadi structbatasan (dan sebagai efek samping tidak dapat kembali nulllagi):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

Sekarang tugas:

myYear = record.GetValueOrNull<int?>("myYear");

Memberikan kesalahan berikut:

Jenis 'int?' harus merupakan tipe nilai yang tidak dapat dibatalkan untuk menggunakannya sebagai parameter 'T' dalam tipe atau metode umum

Apakah menetapkan jenis yang dapat dibatalkan sebagai parameter umum?

Tom Pester
sumber
3
IDataRecordDbDataRecord
Tolong

Jawaban:

262

Ubah tipe pengembalian ke Nullable, dan panggil metode dengan parameter non 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;
}
Greg Dean
sumber
1
Saya sarankan Anda menggunakan "columnValue == DBNull.Value" bukan operator 'is', karena itu sedikit lebih cepat =)
driAn
40
Preferensi pribadi, tetapi Anda dapat menggunakan formulir pendek T? bukannya Nullable <T>
Dunc
11
Ini bagus untuk tipe nilai, tapi kemudian saya pikir itu tidak akan bekerja sama sekali dengan tipe referensi (mis. GetValueOrNull <string>) karena C # sepertinya tidak menyukai Nullable <(tipe ref)> seperti "string?". Solusi Robert C Barth & James Jones, di bawah ini, tampak jauh lebih baik bagi saya jika itu kebutuhan Anda.
bacar
2
@ Bacac - benar, maka "di mana T: struct", jika Anda ingin jenis referensi Anda dapat membuat metode yang sama dengan "di mana T: kelas"
Greg Dean
4
@Reg - tentu saja, tetapi kemudian Anda perlu metode kedua, dan Anda tidak bisa membebani namanya. Seperti yang saya katakan, jika Anda ingin menangani tipe val dan ref, saya pikir solusi yang lebih bersih disajikan di halaman ini.
bacar
107
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

    if (!(val is DBNull))
        return (T)val;

    return default(T);
}

Gunakan saja seperti ini:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
James Jones
sumber
6
Ini dapat disingkat menjadi: return rdr.IsDBNull (index)? default (T): (T) rdr [index];
Bodoh
11
Saya pikir pertanyaan ini secara eksplisit ingin null , bukan default (T) .
mafu
5
@ mafu default (T) akan mengembalikan nol untuk tipe referensi, dan 0 untuk tipe numerik, menjadikan solusi lebih fleksibel.
James Jones
2
Saya pikir lebih baik untuk memanggil ini GetValueOrDefaultuntuk mengklarifikasi bahwa ia kembali default(T)daripada null. Atau, Anda bisa meminta pengecualian jika Ttidak dapat dibatalkan.
Sam
Metode ini memiliki banyak keuntungan, dan memaksa Anda berpikir untuk mengembalikan sesuatu selain null juga.
Shane
61

Lakukan saja dua hal pada kode asli Anda - hapus wherekendala, dan ubah yang terakhir returndari return nullmenjadireturn default(T) . Dengan cara ini Anda dapat mengembalikan tipe apa pun yang Anda inginkan.

Omong-omong, Anda dapat menghindari penggunaan isdengan mengubah ifpernyataan Anda menjadi if (columnValue != DBNull.Value).

Robert C. Barth
sumber
4
Solusi ini tidak berfungsi, karena ada perbedaan logis antara NULL dan 0
Greg Dean
15
Ini berfungsi jika tipe yang dilewatinya int? Ini akan mengembalikan NULL, seperti yang dia inginkan. Jika ia meneruskan int sebagai tipe, itu akan mengembalikan 0 karena sebuah int tidak boleh NULL. Selain fakta bahwa saya mencobanya dan berfungsi dengan baik.
Robert C. Barth
2
Ini adalah jawaban yang paling benar dan fleksibel. Namun, return defaultsudah cukup (Anda tidak perlu (T), kompiler akan mengambilnya dari tipe pengembalian tanda tangan).
McGuireV10
5

Penafian: Jawaban ini berfungsi, tetapi dimaksudkan hanya untuk tujuan pendidikan. :) Solusi James Jones mungkin adalah yang terbaik di sini dan tentu saja yang saya pilih.

dynamicKata kunci C # 4.0 membuat ini lebih mudah, jika kurang aman:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

Sekarang Anda tidak perlu petunjuk tipe eksplisit tentang RHS:

int? value = myDataReader.GetNullableValue("MyColumnName");

Bahkan, Anda bahkan tidak membutuhkannya sama sekali!

var value = myDataReader.GetNullableValue("MyColumnName");

value sekarang akan menjadi int, atau string, atau jenis apa pun yang dikembalikan dari DB.

Satu-satunya masalah adalah bahwa hal ini tidak mencegah Anda menggunakan jenis yang tidak dapat dibatalkan pada LHS, dalam hal ini Anda akan mendapatkan pengecualian runtime yang agak buruk seperti:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

Seperti halnya semua kode yang menggunakan dynamic: caveat coder.

Ian Kemp
sumber
4

Hanya harus melakukan sesuatu yang luar biasa mirip dengan ini. Kode saya:

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;
}
Toby
sumber
3

Saya pikir Anda ingin menangani tipe referensi dan tipe struct. Saya menggunakannya untuk mengkonversi string Elemen XML ke tipe yang lebih diketik. Anda dapat menghapus nullAlternative dengan refleksi. Penyedia format adalah untuk menangani ketergantungan budaya '.' atau pemisah ',' misalnya desimal atau int dan ganda. Ini mungkin berhasil:

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

Anda bisa menggunakannya seperti ini:

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);
Roland Roos
sumber
2

Ini mungkin sebuah utas yang mati, tetapi saya cenderung menggunakan yang berikut ini:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
Ryan Horch
sumber
1
"Tipe 'T' harus merupakan tipe nilai yang tidak dapat dibatalkan untuk menggunakannya sebagai parameter 'T' dalam tipe generik atau metode 'Nullable <T>'"
Ian Warburton
1

Saya sendiri mengalami masalah yang sama.

... = reader["myYear"] as int?; bekerja dan bersih.

Ini bekerja dengan jenis apa pun tanpa masalah. Jika hasilnya DBNull, hasilnya nol karena konversi gagal.

Hele
sumber
Bahkan, Anda mungkin bisa melakukan int v=reader["myYear"]??-1;atau beberapa standar lain, bukan -1. Namun, ini mungkin memunculkan masalah jika nilainya DBNull...
nurchi
1

Saya tahu ini sudah tua, tetapi di sini ada solusi lain:

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

Sekarang, Anda tidak peduli apakah Titu nilai atau tipe referensi. Hanya jika fungsi mengembalikan true, Anda memiliki nilai wajar dari database. Pemakaian:

...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

Pendekatan ini sangat mirip dengan int.TryParse("123", out MyInt);

nurchi
sumber
Akan lebih baik jika Anda mengerjakan konvensi penamaan Anda. Mereka kurang konsisten. Di satu tempat ada variabel tanpa modal maka ada satu dengan. Sama dengan parameter untuk metode.
Marino Šimić
1
Dilakukan dan dilakukan! Kode harapan terlihat lebih baik sekarang. Bob, bibimu :) Semuanya skookum
nurchi
0

Beberapa kendala umum tidak dapat digabungkan dalam mode ATAU (kurang ketat), hanya dalam mode AND (lebih ketat). Berarti satu metode tidak dapat menangani kedua skenario. Batasan umum juga tidak dapat digunakan untuk membuat tanda tangan unik untuk metode ini, jadi Anda harus menggunakan 2 nama metode terpisah.

Namun, Anda dapat menggunakan batasan umum untuk memastikan bahwa metode tersebut digunakan dengan benar.

Dalam kasus saya, saya secara khusus ingin null dikembalikan, dan tidak pernah nilai default dari semua tipe nilai yang mungkin. GetValueOrDefault = buruk. GetValueOrNull = bagus.

Saya menggunakan kata "Null" dan "Nullable" untuk membedakan antara tipe referensi dan tipe nilai. Dan di sini adalah contoh dari beberapa metode ekstensi yang saya tulis yang memuji metode FirstOrDefault di kelas 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;
    }
Casey Plummer
sumber
0

Cara yang lebih pendek:

public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

kembali 0untuk int, dan nulluntukint?

Amirhossein Yari
sumber