Loại Nullable như một tham số chung có thể?


287

Tôi muốn làm một cái gì đó như thế này:

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

Lưu ý loại nullable là tham số chung.

GetValueOrNullhàm có thể trả về null nên lần thử đầu tiên của tôi là:

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

Nhưng lỗi tôi nhận được bây giờ là:

Loại 'int?' phải là loại tham chiếu để sử dụng nó làm tham số 'T' trong loại hoặc phương thức chung

Đúng! Nullable<int>là một struct! Vì vậy, tôi đã cố gắng thay đổi ràng buộc lớp thành một structràng buộc (và vì tác dụng phụ không thể trả lại nullnữa):

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

Bây giờ nhiệm vụ:

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

Đưa ra lỗi sau:

Loại 'int?' phải là loại giá trị không thể rỗng để sử dụng nó làm tham số 'T' trong loại hoặc phương thức chung

Là chỉ định một loại nullable là một tham số chung có thể?


3
Xin vui lòng làm cho chữ ký của bạn IDataRecordtừ DbDataRecord..
nawfal

Câu trả lời:


262

Thay đổi kiểu trả về thành Nullable và gọi phương thức với tham số không 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;
}

1
Tôi khuyên bạn nên sử dụng "cộtValue == DBNull.Value" thay vì toán tử 'is', bởi vì nó nhanh hơn một chút =)
DriAn

40
Sở thích cá nhân, nhưng bạn có thể sử dụng mẫu T ngắn? thay vì Nullable <T>
Dunc

11
Điều này tốt cho các loại giá trị, nhưng sau đó tôi nghĩ nó hoàn toàn không hoạt động với các loại tham chiếu (ví dụ: GetValueOrNull <string>) vì C # dường như không thích Nullable <(ref type)> như "chuỗi?". Các giải pháp của Robert C Barth & James Jones, bên dưới, có vẻ tốt hơn đối với tôi nếu đó là nhu cầu của bạn.
thịt xông khói

2
@bacar - đúng, do đó là "where T: struct", nếu bạn muốn các kiểu tham chiếu, bạn có thể tạo một phương thức tương tự với "where T: class"
Greg Dean

4
@Greg - chắc chắn, nhưng sau đó bạn cần một phương thức thứ hai và bạn không thể quá tải tên. Như tôi nói, nếu bạn muốn xử lý cả hai loại val và ref, tôi nghĩ các giải pháp sạch hơn được trình bày trên trang này.
thịt xông khói

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

Chỉ cần sử dụng nó như thế này:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);

6
Điều này có thể được rút ngắn thành: returnrdr.IsDBNull (index)? mặc định (T): (T) rdr [index];
Đánh lừa

11
Tôi nghĩ rằng câu hỏi này rõ ràng muốn null , không phải mặc định (T) .
mafu

5
@mafu mặc định (T) sẽ trả về null cho các loại tham chiếu và 0 cho các loại số, làm cho giải pháp linh hoạt hơn.
James Jones

2
Tôi nghĩ sẽ rõ ràng hơn khi gọi điều này GetValueOrDefaultđể làm rõ rằng nó trả lại default(T)chứ không phải null. Ngoài ra, bạn có thể yêu cầu nó ném một ngoại lệ nếu Tkhông thể rỗng.
Sam

Phương pháp này có rất nhiều lợi thế và buộc bạn phải suy nghĩ về việc trả lại một cái gì đó ngoài null.
Shane

61

Chỉ cần thực hiện hai điều với mã gốc của bạn - loại bỏ whereràng buộc và thay đổi cuối cùng returntừ return nullthành return default(T). Bằng cách này bạn có thể trả lại bất cứ loại nào bạn muốn.

Nhân tiện, bạn có thể tránh việc sử dụng isbằng cách thay đổi ifcâu lệnh của bạn thành if (columnValue != DBNull.Value).


4
Giải pháp này không hoạt động, vì có sự khác biệt logic giữa NULL và 0
Greg Dean

15
Nó hoạt động nếu loại anh ta vượt qua là int?. Nó sẽ trả về NULL, giống như anh ta muốn. Nếu anh ta chuyển int thành kiểu, nó sẽ trả về 0 vì int không thể là NULL. Bên cạnh thực tế là tôi đã thử nó và nó hoạt động hoàn hảo.
Robert C. Barth

2
Đây là câu trả lời đúng và linh hoạt nhất. Tuy nhiên, return defaultlà đủ (bạn không cần (T), trình biên dịch sẽ suy ra từ kiểu trả về chữ ký).
McGuireV10

5

Disclaimer: Câu trả lời này hoạt động, nhưng chỉ dành cho mục đích giáo dục. :) Giải pháp của James Jones có lẽ là tốt nhất ở đây và chắc chắn là giải pháp tôi muốn đi cùng.

dynamicTừ khóa của C # 4.0 làm cho việc này thậm chí còn dễ dàng hơn, nếu ít an toàn hơn:

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

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

Bây giờ bạn không cần gợi ý loại rõ ràng trên RHS:

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

Trên thực tế, bạn thậm chí không cần nó!

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

value bây giờ sẽ là một int, hoặc một chuỗi hoặc bất kỳ kiểu nào được trả về từ DB.

Vấn đề duy nhất là điều này không ngăn bạn sử dụng các loại không thể rỗng trên LHS, trong trường hợp đó bạn sẽ gặp một ngoại lệ thời gian chạy khá khó chịu như:

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

Như với tất cả các mã sử dụng dynamic: coder caveat.


4

Chỉ cần phải làm một cái gì đó đáng kinh ngạc tương tự như thế này. Mã của tôi:

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

3

Tôi nghĩ rằng bạn muốn xử lý các loại tham chiếu và các loại cấu trúc. Tôi sử dụng nó để chuyển đổi các chuỗi Phần tử XML thành một kiểu được gõ nhiều hơn. Bạn có thể loại bỏ nullAlternative với sự phản chiếu. Trình định dạng là để xử lý văn hóa phụ thuộc '.' hoặc dấu phân cách ',' trong ví dụ số thập phân hoặc số nguyên và số nhân. Điều này có thể làm việc:

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

Bạn có thể sử dụng nó như thế này:

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

2

Đây có thể là một chủ đề chết, nhưng tôi có xu hướng sử dụng như sau:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}

1
"Loại 'T' phải là loại giá trị không thể rỗng để sử dụng nó làm tham số 'T' trong loại hoặc phương thức chung 'Nullable <T>'"
Ian Warburton

1

Tôi chỉ gặp phải vấn đề tương tự bản thân mình.

... = reader["myYear"] as int?; làm việc và sạch sẽ

Nó hoạt động với bất kỳ loại mà không có vấn đề. Nếu kết quả là DBNull, nó sẽ trả về null khi chuyển đổi không thành công.


Trong thực tế, bạn có thể có thể làm int v=reader["myYear"]??-1;hoặc một số mặc định khác thay vì -1. Tuy nhiên, điều này có thể đưa ra các vấn đề nếu giá trị là DBNull...
nurchi

1

Tôi biết điều này đã cũ, nhưng đây là một giải pháp khác:

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

Bây giờ, bạn không quan tâm nếu Tlà giá trị hoặc loại tham chiếu. Chỉ khi hàm trả về true, bạn mới có giá trị hợp lý từ cơ sở dữ liệu. Sử dụng:

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

Cách tiếp cận này rất giống với int.TryParse("123", out MyInt);


Sẽ tốt hơn nếu bạn làm việc theo các quy ước đặt tên của bạn. Họ thiếu tính nhất quán. Ở một nơi có một biến không có vốn thì có một với. Tương tự với các tham số cho các phương thức.
Marino imić

1
Ngay và luôn! Hy vọng mã có vẻ tốt hơn bây giờ. Bob là dì của bạn :) Tất cả là skookum
nurchi

0

Nhiều ràng buộc chung không thể được kết hợp theo kiểu OR (ít hạn chế hơn), chỉ theo kiểu AND (hạn chế hơn). Có nghĩa là một phương thức không thể xử lý cả hai kịch bản. Các ràng buộc chung cũng không thể được sử dụng để tạo chữ ký duy nhất cho phương thức, vì vậy bạn phải sử dụng 2 tên phương thức riêng biệt.

Tuy nhiên, bạn có thể sử dụng các ràng buộc chung để đảm bảo rằng các phương thức được sử dụng đúng.

Trong trường hợp của tôi, tôi đặc biệt muốn null được trả về và không bao giờ là giá trị mặc định của bất kỳ loại giá trị nào có thể. GetValueOrDefault = xấu. GetValueOrNull = tốt.

Tôi đã sử dụng các từ "Null" và "Nullable" để phân biệt giữa các loại tham chiếu và loại giá trị. Và đây là một ví dụ về một vài phương thức mở rộng mà tôi đã viết, khen ngợi phương thức FirstOrDefault trong lớp 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;
    }

0

Cách ngắn hơn:

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

trả lại 0cho intnullchoint?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.