ชนิดที่ Nullable เป็นพารามิเตอร์ทั่วไปได้หรือไม่


288

ฉันต้องการทำสิ่งนี้:

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

สังเกตเห็นว่า nullable type เป็นพารามิเตอร์ทั่วไป

เนื่องจาก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' ในประเภทหรือวิธีการทั่วไป

ขวา! 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' ในประเภทหรือวิธีการทั่วไป

การระบุประเภท nullable เป็นพารามิเตอร์ทั่วไปที่เป็นไปได้ทั้งหมดหรือไม่


3
กรุณาทำลายเซ็นของคุณIDataRecordจากDbDataRecord..
nawfal

คำตอบ:


262

เปลี่ยนประเภทส่งคืนเป็น 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
ผมขอแนะนำให้คุณใช้ "columnValue == DBNull.Value" แทนของผู้ประกอบการ 'เป็น' เพราะมันเร็วขึ้นเล็กน้อย =)
Drian

40
การตั้งค่าส่วนตัว แต่คุณสามารถใช้แบบฟอร์มสั้น T? แทน Nullable <T>
Dunc

11
สิ่งนี้ใช้ได้สำหรับประเภทค่า แต่ฉันคิดว่ามันจะไม่ทำงานกับประเภทการอ้างอิงทั้งหมด (เช่น GetValueOrNull <string>) เพราะ C # ดูเหมือนจะไม่ชอบ Nullable <(ประเภท ref)> เช่น "string?" วิธีแก้ปัญหาของ Robert C Barth และ James Jones ดูดีกว่าสำหรับฉันหากคุณต้องการ
บาคาร่า

2
@bacar - ขวาดังนั้น "โดยที่ T: struct" หากคุณต้องการประเภทการอ้างอิงคุณสามารถสร้างวิธีการที่คล้ายกันกับ "where T: class"
Greg Dean

4
@Greg - แน่นอน แต่คุณต้องใช้วิธีที่สองและคุณไม่สามารถโหลดชื่อมากเกินไปได้ อย่างที่ฉันพูดถ้าคุณต้องการจัดการกับทั้งประเภทและประเภทอ้างอิงฉันคิดว่ามีวิธีแก้ปัญหาที่สะอาดกว่าในหน้านี้
บาคาร่า

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

เพียงใช้มันเช่นนี้

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

6
สิ่งนี้สามารถย่อให้เป็น: return rdr.IsDBNull (ดัชนี) ได้หรือไม่ เริ่มต้น (T): (T) rdr [ดัชนี];
Foole

11
ผมคิดว่าคำถามนี้อย่างชัดเจนต้องการnullไม่เริ่มต้น (T)
mafu

5
@mafu default (T) จะส่งคืนค่า null สำหรับประเภทการอ้างอิงและ 0 สำหรับประเภทตัวเลขทำให้โซลูชันมีความยืดหยุ่น
James Jones

2
ฉันคิดว่ามันชัดเจนอย่างใดอย่างหนึ่งเรียกสิ่งนี้GetValueOrDefaultจะชี้แจงว่ามันจะกลับมากกว่าdefault(T) nullอีกวิธีหนึ่งคือคุณอาจมีข้อยกเว้นถ้าTไม่เป็นโมฆะ
Sam

วิธีนี้มีข้อดีมากมายและบังคับให้คุณคิดถึงการส่งคืนสิ่งอื่นที่ไม่ใช่ null เช่นกัน
เชน

61

เพียงแค่ทำสองสิ่งที่รหัสต้นฉบับของคุณ - ลบwhereข้อ จำกัด และการเปลี่ยนแปลงที่ผ่านมาreturnจากการreturn null return default(T)วิธีนี้คุณสามารถคืนค่าชนิดใดก็ได้ที่คุณต้องการ

โดยวิธีการที่คุณสามารถหลีกเลี่ยงการใช้isโดยการเปลี่ยนคำสั่งให้ifif (columnValue != DBNull.Value)


4
วิธีนี้ใช้ไม่ได้เนื่องจากมีความแตกต่างทางตรรกะระหว่าง NULL และ 0
Greg Dean

15
มันทำงานได้ดีถ้าประเภทที่เขาผ่านมีค่าหรือไม่ มันจะคืนค่า NULL เหมือนที่เขาต้องการ ถ้าเขาผ่าน int เป็นชนิดมันจะคืนค่า 0 เนื่องจาก int ไม่สามารถเป็น NULL ได้ นอกเหนือจากความจริงที่ว่าฉันลองและมันก็ทำงานได้อย่างสมบูรณ์
Robert C. Barth

2
นี่คือคำตอบที่ถูกต้องและยืดหยุ่นที่สุด อย่างไรก็ตามreturn defaultก็เพียงพอแล้ว (คุณไม่ต้องการ(T)คอมไพเลอร์จะอนุมานจากประเภทการส่งคืนลายเซ็น)
McGuireV10

5

คำเตือน:คำตอบนี้ใช้ได้ แต่มีวัตถุประสงค์เพื่อการศึกษาเท่านั้น :) ทางออกของ James Jones น่าจะดีที่สุดที่นี่และแน่นอนว่าฉันจะไปด้วย

dynamicคำหลักของ C # 4.0 ทำให้สิ่งนี้ง่ายยิ่งขึ้นถ้าปลอดภัยน้อยลง:

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: coder caveat


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

3

ฉันคิดว่าคุณต้องการจัดการประเภทการอ้างอิงและประเภทโครงสร้าง ฉันใช้มันเพื่อแปลงสตริงองค์ประกอบ XML เป็นประเภทที่พิมพ์ได้มากกว่า คุณสามารถลบ nullAlternative ด้วยการสะท้อน formatprovider คือการจัดการกับวัฒนธรรม '.' หรือ ',' ตัวคั่นในเช่นทศนิยมหรือ 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);

2

นี่อาจเป็นเธรดที่ตายแล้ว แต่ฉันมักจะใช้สิ่งต่อไปนี้:

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

1
"ประเภท 'T' จะต้องเป็นประเภทค่าที่ไม่เป็นโมฆะเพื่อใช้เป็นพารามิเตอร์ 'T' ในประเภททั่วไปหรือวิธีการ 'Nullable <T>'"
Ian Warburton

1

ฉันเพิ่งพบปัญหาเดียวกันด้วยตัวเอง

... = reader["myYear"] as int?; ใช้งานได้และสะอาด

มันทำงานได้กับทุกประเภทโดยไม่มีปัญหา หากผลลัพธ์คือ DBNull จะส่งคืนค่า null เมื่อการแปลงล้มเหลว


ในความเป็นจริงคุณอาจจะทำint v=reader["myYear"]??-1;หรือบางส่วนเริ่มต้นอื่น ๆ -1แทน อย่างไรก็ตามสิ่งนี้อาจทำให้เกิดปัญหาขึ้นถ้าค่าคือDBNull...
nurchi

1

ฉันรู้ว่านี่เก่า แต่นี่เป็นวิธีแก้ปัญหาอื่น:

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เป็นค่าหรือประเภทการอ้างอิง เฉพาะเมื่อฟังก์ชันคืนค่าเป็นจริงคุณจะได้รับค่าที่เหมาะสมจากฐานข้อมูล การใช้งาน:

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

วิธีนี้คล้ายกับ int.TryParse("123", out MyInt);


มันจะดีถ้าคุณทำงานในแบบแผนการตั้งชื่อของคุณ พวกเขาขาดความมั่นคง ในที่เดียวมีตัวแปรที่ไม่มีตัวพิมพ์ใหญ่แล้วก็มีอีกตัวด้วย เช่นเดียวกับพารามิเตอร์วิธีการ
Marino Šimić

1
เสร็จแล้ว! ตอนนี้โค้ดหวังว่าจะดูดีขึ้น บ็อบเป็นป้าของคุณ :) ทุกอย่างเป็น skookum
nurchi

0

ข้อ จำกัด ทั่วไปหลายข้อไม่สามารถรวมกันใน 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;
    }

0

วิธีที่สั้นกว่า:

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

กลับ0สำหรับintและnullสำหรับint?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.