เหตุใดการคัดเลือก int เป็นค่า enum ที่ไม่ถูกต้องจึงไม่ทำให้เกิดข้อยกเว้น


124

ถ้าฉันมี enum ดังนี้:

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

มันไม่ทำไมไม่โยนยกเว้นเมื่อหล่อintที่อยู่นอกค่าเหล่านี้ประเภทของBeer?

ตัวอย่างเช่นรหัสต่อไปนี้ไม่มีข้อยกเว้น แต่จะส่งออก '50' ไปที่คอนโซล:

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

เจอแบบนี้แปลก ๆ ... ใครช่วยชี้แจงหน่อย


27
โปรดทราบว่าคุณสามารถตรวจสอบได้เสมอว่าค่านั้นใช้ได้กับEnumหรือไม่
Tim Schmelter

เจ๋งฉันไม่รู้อาจจะใช้ในปัญหาที่ฉันกำลังพยายามแก้ไขอยู่
jcvandan

4
แต่อ่านสิ่งนี้ด้วย: goldfishforthought.blogspot.com/2008/03/…
Tim Schmelter

1
โหวตให้ Stella มีมูลค่ามากกว่า Bud ถึงสองเท่า
Dan Bechard

คำตอบ:


81

นำมาจากความสับสนกับการแยกวิเคราะห์ Enum

นี่เป็นการตัดสินใจของคนที่สร้าง. NET Enum ที่มีการสนับสนุนจากประเภทค่าอื่น ( int, short, byteฯลฯ ) และเพื่อให้มันจริงจะมีค่าใด ๆ ที่ถูกต้องสำหรับประเภทค่าเหล่านั้น

โดยส่วนตัวแล้วฉันไม่ใช่แฟนตัวยงของวิธีการนี้ดังนั้นฉันจึงสร้างชุดวิธียูทิลิตี้:

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}


public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

ด้วยวิธีนี้ฉันสามารถพูดได้ว่า:

if(!sEnum.IsDefined()) throw new Exception(...);

... หรือ:

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

แก้ไข

นอกเหนือจากคำอธิบายที่ให้ไว้ข้างต้นคุณต้องตระหนักว่า Enum เวอร์ชัน. NET เป็นไปตามรูปแบบที่ได้รับแรงบันดาลใจจาก C มากกว่ารูปแบบที่ได้รับแรงบันดาลใจจาก Java สิ่งนี้ทำให้สามารถมีenums "Bit Flag"ซึ่งสามารถใช้รูปแบบไบนารีเพื่อพิจารณาว่า "แฟล็ก" ที่เฉพาะเจาะจงนั้นทำงานอยู่ในค่า enum หรือไม่ ถ้าคุณต้องกำหนดได้ทุกชุดของธง (เช่นMondayAndTuesday, MondayAndWednesdayAndThursday) เหล่านี้จะเป็นที่น่าเบื่อมาก ดังนั้นการมีความสามารถในการใช้ค่า enum ที่ไม่ได้กำหนดจึงมีประโยชน์มาก เพียงแค่ต้องทำงานเพิ่มเติมเล็กน้อยเมื่อคุณต้องการพฤติกรรมที่ล้มเหลวอย่างรวดเร็วในประเภท enum ที่ไม่ใช้ประโยชน์จากกลอุบายเหล่านี้


ดี ... ฉันไม่ใช่แฟนตัวยงของวิธีการทำงานเช่นกันดูเหมือนแปลกสำหรับฉัน
jcvandan

3
@dormisher: "'บ้าไปแล้ว แต่ยังมีวิธีที่จะไม่ทำ' ดูการแก้ไขของฉัน
StriplingWarrior

2
@StriplingWarrior สำหรับความสนใจ เทียบเท่ากับ Java คือ EnumSet ของ (วันจันทร์วันพุธวันพฤหัสบดี) ภายใน EnumSet มีความยาวเพียงชุดเดียว ดังนั้นให้ API ที่ดีโดยไม่สูญเสียประสิทธิภาพมากนัก
Iain


@StriplingWarrior ในวิธีการแยกวิเคราะห์ของคุณคุณกำลังพยายามเรียก IsDefined เหมือนวิธีการขยาย อย่างไรก็ตามวิธี IsDefined ไม่สามารถสร้างวิธีการขยายได้เนื่องจากวิธีที่คุณพยายามสร้างคลาส EnumUtil ด้วยข้อ จำกัด (เพื่อทำการตรวจสอบคงที่สำหรับ enum ซึ่งฉันชอบมาก) อย่างไรก็ตามมีผลบอกฉันว่าเราไม่สามารถสร้างวิธีการขยายในคลาสทั่วไปได้ ความคิดใด ๆ ?
CJC

55

Enums มักใช้เป็นแฟล็ก:

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

ค่าของ p คือจำนวนเต็ม 3 ซึ่งไม่ใช่ค่า enum แต่เห็นได้ชัดว่าเป็นค่าที่ถูกต้อง

โดยส่วนตัวแล้วฉันอยากเห็นวิธีแก้ปัญหาที่แตกต่างออกไป ฉันอยากจะมีความสามารถในการสร้างประเภทจำนวนเต็ม "บิตอาร์เรย์" และ "ชุดของค่าที่แตกต่างกัน" เป็นคุณลักษณะภาษาที่แตกต่างกันสองแบบแทนที่จะรวมทั้งสองเป็น "enum" แต่นั่นคือสิ่งที่นักออกแบบภาษาและกรอบการทำงานดั้งเดิมคิดขึ้นมา ด้วยเหตุนี้เราจึงต้องอนุญาตให้ค่า enum ที่ไม่ได้ประกาศเป็นค่าทางกฎหมาย


19
แต่ 3 ประเภท "ชัดเจนเป็นค่าที่ถูกต้อง" ทำให้จุดของ OP แข็งแกร่งขึ้น แอตทริบิวต์ [Flags] นั้นสามารถบอกให้คอมไพเลอร์ค้นหา enum ที่กำหนดหรือค่าที่ถูกต้อง แค่บอกว่า
LarsTech

15

คำตอบสั้น ๆ : นักออกแบบภาษาตัดสินใจออกแบบภาษาด้วยวิธีนี้

คำตอบยาว ๆ : Section 6.2.2: Explicit enumeration conversionsของข้อกำหนดภาษา C # กล่าวว่า:

การแปลงการแจงนับอย่างชัดเจนระหว่างสองประเภทจะได้รับการประมวลผลโดยถือว่า enum-type ใด ๆ ที่เข้าร่วมเป็นประเภทพื้นฐานของ enum-type จากนั้นจึงทำการแปลงตัวเลขโดยนัยหรืออย่างชัดเจนระหว่างประเภทผลลัพธ์ ตัวอย่างเช่นกำหนดประเภท E ที่มีและประเภท int ที่อยู่การแปลงจาก E เป็นไบต์จะถูกประมวลผลเป็นการแปลงตัวเลขอย่างชัดเจน (§6.2.1) จาก int เป็นไบต์และการแปลงจากไบต์เป็น E จะถูกประมวลผลเป็น การแปลงตัวเลขโดยนัย (§6.1.2) จากไบต์เป็น int

โดยทั่วไปenumจะถือว่าเป็นชนิดพื้นฐานเมื่อต้องดำเนินการแปลง โดยค่าเริ่มต้นenum 's ประเภทเป็นพื้นฐานซึ่งหมายถึงการแปลงได้รับการปฏิบัติเหมือนการแปลงไปInt32 Int32ซึ่งหมายความว่าintอนุญาตให้ใช้ค่าที่ถูกต้องได้

ฉันสงสัยว่าสิ่งนี้ทำด้วยเหตุผลด้านประสิทธิภาพเป็นหลัก ด้วยการสร้างenumประเภทอินทิกรัลอย่างง่ายและอนุญาตการแปลงประเภทอินทิกรัล CLR จึงไม่จำเป็นต้องทำการตรวจสอบเพิ่มเติมทั้งหมด ซึ่งหมายความว่าการใช้ an enumไม่มีการสูญเสียประสิทธิภาพใด ๆ เมื่อเทียบกับการใช้จำนวนเต็มซึ่งจะช่วยส่งเสริมการใช้งาน


คุณคิดว่าสิ่งนี้แปลกหรือไม่? ฉันคิดว่าหนึ่งในประเด็นหลักของ enum คือการจัดกลุ่มจำนวนเต็มอย่างปลอดภัยซึ่งเราสามารถระบุความหมายของแต่ละบุคคลได้เช่นกัน สำหรับฉันการอนุญาตให้ประเภท enum เก็บค่าจำนวนเต็มใด ๆ จะทำให้จุดประสงค์ของมันพ่ายแพ้
jcvandan

@dormisher: ฉันเพิ่งแก้ไขและเพิ่มเล็กน้อยในตอนท้าย มีค่าใช้จ่ายในการจัดหา "ความปลอดภัย" ที่คุณต้องการ ที่กล่าวมาโดยการใช้งานปกตินี่ไม่ใช่ปัญหาเลย
Reed Copsey

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