كيف يتم استنباط التعداد من النظام. وهو عدد صحيح في نفس الوقت؟

StackOverflow https://stackoverflow.com/questions/4626394

سؤال

يحرر: تعليقات في أسفل. ايضا، هذه.


هذا ما هو نوع من الخلط علي. أفهم أنه إذا كان لدي مثل هذا ...

enum Animal
{
    Dog,
    Cat
}

... ما قمت به بشكل أساسي هو محدد أ نوع القيمة اتصل Animal مع قيمتين محددين ، Dog و Cat. هذا النوع مستمد من نوع مرجع System.Enum (شيء لا يمكن أن تفعله أنواع القيمة عادة - على الأقل ليس في C# - ولكن المسموح به في هذه الحالة) ، ولديه منشأة للالتفاف ذهابًا وإيابًا/من/من int القيم.

إذا كانت الطريقة التي وصفتها للتو نوع التعداد أعلاه صحيحة ، فأتوقع أن يرمي الرمز التالي InvalidCastException:

public class Program
{
    public static void Main(string[] args)
    {
        // Box it.
        object animal = Animal.Dog;

        // Unbox it. How are these both successful?
        int i = (int)animal;
        Enum e = (Enum)animal;

        // Prints "0".
        Console.WriteLine(i);

        // Prints "Dog".
        Console.WriteLine(e);
    }
}

عادة، لا يمكنك فك نوع القيمة من System.Object مثل أي شيء آخر غير نوعه الدقيق. فكيف يمكن أعلاه ممكن؟ يبدو الأمر كما لو Animal يكتب هو و int (ليس مجرد قابلة للتحويل إلى int) و هو و Enum (ليس مجرد قابلة للتحويل إلى Enum) في نفس الوقت. هل هو ميراث متعدد؟ يفعل System.Enum يرث بطريقة أو بأخرى من System.Int32 (شيء لم أتوقع أن يكون ممكنًا)؟

يحرر: لا يمكن أن يكون أي مما سبق. يوضح الرمز التالي هذا (على ما أظن) بشكل قاطع:

object animal = Animal.Dog;

Console.WriteLine(animal is Enum);
Console.WriteLine(animal is int);

المخرجات أعلاه:

True
False

كلاهما وثائق MSDN حول التعدادات ومواصفات C# تستفيد من مصطلح "النوع الأساسي" ؛ لكنني لا أعرف ماذا يعني هذا ، ولم أسمعه من قبل يستخدم في إشارة إلى أي شيء آخر غير التعدادات. ماذا يفعل "النوع الأساسي" في الواقع يعني?


لذلك ، هل هذا آخر الحالة التي تحصل على علاج خاص من CLR?

أموالي على هذا الأمر ... لكن الإجابة/التفسير ستكون لطيفة.


تحديث: damien_the_unbeliever قدمت الإشارة إلى الإجابة حقًا على هذا السؤال. يمكن العثور على التفسير في القسم الثاني من مواصفات CLI ، في القسم الخاص بالتعداد:

لأغراض الربط (على سبيل المثال ، لتحديد تحديد تعريف الطريقة من مرجع الطريقة المستخدمة للاتصال به) ، يجب أن تكون التعدادات متميزة عن نوعها الأساسي. لجميع الأغراض الأخرى ، بما في ذلك التحقق من التعليمات البرمجية وتنفيذها ، التعداد غير المربع يتداخل بحرية مع نوعه الأساسي. يمكن أن تكون التعدادات محاصرًا بنوع مثيل محاصر المقابل ، لكن هذا النوع هو ليس نفس النوع المحاصر من النوع الأساسي ، لذلك لا تفقد الملاكمة النوع الأصلي من التعداد.

تحرير (مرة أخرى؟!): انتظر ، في الواقع ، لا أعرف أنني قرأت ذلك بشكل صحيح في المرة الأولى. ربما لا يفسر 100 ٪ سلوك إلغاء اللاصطحاب المتخصص نفسه (على الرغم من أنني أترك إجابة داميان مقبولة ، لأنها تلقي الكثير من الضوء على هذه المسألة). سأستمر في النظر في هذا ...


تحرير آخر: رجل ، ثم إجابة Yodaj007 ألقيني لحلقة أخرى. بطريقة ما ، لا يمثل التعداد تمامًا مثل int; ؛ بعد int يمكن تعيينه لمتغير التعداد مع عدم وجود يلقي؟ بوه؟

أعتقد أن هذا كله مضاء في النهاية إجابة هانز, ، ولهذا السبب قبلته. (آسف ، داميان!)

هل كانت مفيدة؟

المحلول

نعم ، علاج خاص. يدرك برنامج التحويل البرمجي JIT تمامًا طريقة عمل أنواع القيمة المعبأة. وهو بشكل عام ما الذي يجعل أنواع القيمة تتصرف قليلاً. يتضمن الملاكمة إنشاء قيمة system.object التي تتصرف بنفس طريقة قيمة نوع المرجع. عند هذه النقطة ، لم تعد قيم نوع القيمة تتصرف مثل القيم في وقت التشغيل. مما يجعل من الممكن ، على سبيل المثال ، أن يكون لديك طريقة افتراضية مثل ToString (). يحتوي الكائن المحاصر على مؤشر جدول طريقة ، تمامًا مثل أنواع المراجع.

يعرف برنامج التحويل البرمجي JIT مؤشرات جداول الأسلوب لأنواع القيمة مثل int و bool Up في المقدمة. الملاكمة والملاكمة بالنسبة لهم جداً فعال ، يستغرق سوى حفنة من تعليمات رمز الجهاز. هذا يحتاج إلى أن يكون فعالا في .NET 1.0 لجعله قادرا على المنافسة. أ جداً جزء مهم من ذلك هو التقييد الذي لا يمكن إلغاء قيمة نوع نوع القيمة إلا لنفس النوع. هذا يتجنب الارتعاش من الاضطرار إلى إنشاء بيان مفتاح ضخم يستدعي رمز التحويل الصحيح. كل ما يتعين عليه فعله هو التحقق من مؤشر جدول الطريقة في الكائن والتحقق من أنه النوع المتوقع. ونسخ القيمة خارج الكائن مباشرة. ربما يكون هذا التقييد موجودًا في VB.NET ، يقوم مشغل CTYPE () في الواقع بإنشاء رمز إلى وظيفة مساعد تحتوي على بيان التبديل الكبير هذا.

المشكلة في أنواع التعداد هي أن هذا لا يمكن أن ينجح. يمكن أن يكون لدى التعدادات نوع مختلف getUnderyingType (). وبعبارة أخرى ، فإن القيمة غير المربوطة لها أحجام مختلفة ، لذا ببساطة لا يمكن أن تعمل نسخ القيمة من الكائن المحاصر. على دراية تامة ، فإن الارتعاش لم يعد يدخل رمز إلغاء التزامن بعد الآن ، فهو يقوم بإنشاء مكالمة إلى وظيفة المساعد في CLR.

يسمى هذا المساعد jit_unbox () ، يمكنك العثور على رمز المصدر الخاص به في مصدر SSCLI20 ، CLR/SRC/VM/Jithelpers.cpp. سترى أنها تتعامل مع أنواع التعدادات بشكل خاص. إنه متسامح ، فهو يتيح إلغاء التفسير من نوع التعداد إلى آخر. ولكن فقط إذا كان النوع الأساسي هو نفسه ، فستحصل على invalidcastexception إذا لم يكن هذا هو الحال.

وهو أيضًا سبب إعلان التعداد كفئة. انها منطقي السلوك من نوع مرجعي ، يمكن إلقاء أنواع التعداد المشتقة من واحد إلى آخر. مع القيود المذكورة أعلاه على توافق النوع الأساسي. ومع ذلك ، فإن قيم نوع التعداد لها سلوك قيمة نوع القيمة إلى حد كبير. لديهم نسخ الدلالات وسلوك الملاكمة.

نصائح أخرى

يتم التعامل مع التعدادات بشكل خاص من قبل CLR. إذا كنت ترغب في الدخول في تفاصيل Gory ، يمكنك تنزيل MS Partition II المواصفات. في ذلك ، ستجد هذا التعداد:

تطيع التعدادات القيود الإضافية التي تتجاوز تلك الموجودة في أنواع القيمة الأخرى. يجب أن تحتوي التعدادات على حقول فقط كأعضاء (لا يجوز لها حتى تحديد المهيئات من النوع أو منشئات المثيلات) ؛ لا يجوز لهم تنفيذ أي واجهات ؛ يجب أن يكون لديهم تخطيط حقل تلقائي (الفقرة 10.1.2) ؛ يجب أن يكون لديهم حقل مثيل واحد بالضبط ويجب أن يكون من النوع الأساسي من التعداد ؛ يجب أن تكون جميع الحقول الأخرى ثابتة وحرفية (الفقرة 16.1) ؛

هكذا يمكن أن يرثوا من System.Enum ، ولكن لديهم نوع "أساسي" - إنه حقل مثيل واحد يسمح له.

هناك أيضًا مناقشة حول سلوك الملاكمة ، لكنها لا تصف إلغاء التخلص منها بشكل صريح للنوع الأساسي ، الذي يمكنني رؤيته.

ينص القسم i ، 8.5.2 على أن التعدادات هي "اسم بديل لنوع موجود" ولكن [f] أو أغراض التوقيعات المطابقة ، يجب ألا يكون التعداد هو نفسه النوع الأساسي."

القسم II ، 14.3 يوضح: "لجميع الأغراض الأخرى ، بما في ذلك التحقق من الكود وتنفيذها ، تعداد غير مرن يتداخل بحرية بنوعه الأساسي. نوع النوع الأساسي ، لذلك لا تفقد الملاكمة النوع الأصلي من التعداد. "

يشرح القسم الثالث ، 4.32 سلوك إلغاء التفسير: "نوع من نوع القيمة الواردة في OBJ يجب أن تكون متوافقة مع ValueType. [ملاحظة: هذا يؤثر على السلوك بأنواع التعداد ، انظر القسم II.14.3. ملاحظة نهاية] "

ما أذكره هنا هو من الصفحة 38 من ECMA-335 (أقترح عليك تنزيله فقط للحصول عليه):

يدعم CTS التعداد (المعروف أيضًا باسم نوع التعداد) ، وهو اسم بديل لنوع موجود. لأغراض التوقيعات المطابقة ، يجب ألا يكون التعداد هو نفسه النوع الأساسي. ومع ذلك ، يجب أن تكون مثيلات التعداد قابلة للتخصيص للنوع الأساسي ، والعكس صحيح. وهذا هو ، لا يلزم وجود عدد من الممثلين (انظر الفقرة 8.3.3) أو الإكراه (انظر الفقرة 8.3.2) للتحويل من التعداد إلى النوع الأساسي ، كما أنها ليست مطلوبة من النوع الأساسي إلى التعداد. التعداد مقيد أكثر بكثير من نوع حقيقي ، على النحو التالي:

يجب أن يكون النوع الأساسي نوعًا صحيحًا مدمجًا. يجب أن تستمد التعداد من النظام. وبالتالي فهي أنواع القيمة. مثل جميع أنواع القيمة ، يجب أن تكون مختومة (انظر الفقرة 8.9.9).

enum Foo { Bar = 1 }
Foo x = Foo.Bar;

سيكون هذا البيان خطأ بسبب الجملة الثانية:

x is int

أنهم نكون نفس (اسم مستعار) ، لكن توقيعهم ليس هو نفسه. التحويل من وإلى int ليس طاقم.

من الصفحة 46:

الأنواع الأساسية - في تعدادات CTS هي أسماء بديلة للأنواع الموجودة (الفقرة 8.5.2) ، والتي يطلق عليها نوعها الأساسي. باستثناء مطابقة التوقيع (§8.5.2) يتم التعامل مع التعدادات كنوعها الأساسي. هذه المجموعة الفرعية هي مجموعة من أنواع التخزين مع إزالة التعدادات.

عد إلى تعداد Foo الخاص بي في وقت سابق. سيعمل هذا البيان:

Foo x = (Foo)5;

إذا قمت بفحص رمز IL الذي تم إنشاؤه في طريقتي الرئيسية في Reflector:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
    [0] valuetype ConsoleTesting.Foo x)
L_0000: nop 
L_0001: ldc.i4.5 
L_0002: stloc.0 
L_0003: call string [mscorlib]System.Console::ReadLine()
L_0008: pop 
L_0009: ret 
}

لاحظ أنه لا يوجد يلقي. ldc تم العثور عليه في الصفحة 86. يقوم بتحميل ثابت. i4 تم العثور عليه في الصفحة 151 ، مما يشير إلى أن النوع صحيح 32 بت. لا يوجد طاقم!

مستخرجة من MSDN:

النوع الافتراضي الأساسي لعناصر التعداد هو int. بشكل افتراضي ، يتمتع العداد الأول بالقيمة 0 ، ويتم زيادة قيمة كل عداد متتالي بمقدار 1.

لذا ، فإن فريق الممثلين ممكن ، لكنك تحتاج إلى إجباره:

يحدد النوع الأساسي مقدار التخزين الذي يتم تخصيصه لكل تعداد. ومع ذلك ، هناك حاجة إلى فريق عمل صريح للتحويل من نوع التعداد إلى نوع متكامل.

عندما تقوم بتمرير التعداد الخاص بك في object, كائن الحيوان مشتق من System.Enum (النوع الحقيقي معروف في وقت التشغيل) لذلك هو في الواقع int, ، لذلك الممثلين صالحين.

  • (animal is Enum) عائدات true: لهذا السبب ، يمكنك إلغاء علبة الحيوان في التعداد أو الحدث في إجراء عملية صريحة.
  • (animal is int) عائدات false: ال is لا يتحقق المشغل (بشكل عام الشيك) من النوع الأساسي للعوامل. أيضًا ، لهذا السبب ، تحتاج إلى إجراء صب صريح لتحويل التعداد إلى int.

في حين أن أنواع التعدادات موروثة من System.Enum, ، أي تحويل بينهما ليس مباشرًا ، ولكنه آخر ملاكمة/غير ملاكمة. من C# 3.0 مواصفات:

نوع التعداد هو نوع متميز مع الثوابت المسماة. كل نوع التعداد له نوع أساسي ، يجب أن يكون بايت ، sbyte ، قصيرة ، ushort ، int ، uint ، طويلة أو ulong. مجموعة قيم نوع التعداد هي نفس مجموعة القيم للنوع الأساسي. لا تقتصر قيم نوع التعداد على قيم الثوابت المسماة. يتم تعريف أنواع التعداد من خلال إعلانات التعداد

لذلك ، بينما يتم اشتقاق فصل الحيوانات الخاص بك System.Enum, ، إنه في الواقع int. راجع للشغل ، شيء غريب آخر هو System.Enum مشتق من System.ValueType, ، ومع ذلك لا يزال نوع المرجع.

أ Enumالنوع الأساسي هو النوع المستخدم لتخزين قيمة الثوابت. في مثالك ، على الرغم من أنك لم تحدد بشكل صريح القيم ، فإن C# يقوم بذلك:

enum Animal : int
{
    Dog = 0,
    Cat = 1
}

داخليا، Animal يتكون من ثوابت اثنين من قيم عدد صحيح 0 و 1. ولهذا السبب يمكنك إلقاء عدد صحيح بشكل صريح إلى Animal و Animal إلى عدد صحيح. إذا مررت Animal.Dog إلى معلمة تقبل Animal, ، ما تفعله حقًا هو تمرير قيمة عدد صحيح 32 بت Animal.Dog (في هذه الحالة ، 0). إذا أعطيت Animal نوع جديد أساسي ، ثم يتم تخزين القيم كنوع.

لماذا لا ... إنها صالحة تمامًا ، على سبيل المثال ، للهيكل الذي يحمل INT داخليًا ، وأن يكون قابلاً للتحويل إلى int مع مشغل مصبوب صريح ... يتيح محاكاة التعداد:

interface IEnum { }

struct MyEnumS : IEnum
{
    private int inner;

    public static explicit operator int(MyEnumS val)
    {
        return val.inner;
    }

    public static explicit operator MyEnumS(int val)
    {
        MyEnumS result;
        result.inner = val;
        return result;
    }

    public static readonly MyEnumS EnumItem1 = (MyEnumS)0;
    public static readonly MyEnumS EnumItem2 = (MyEnumS)2;
    public static readonly MyEnumS EnumItem3 = (MyEnumS)10;

    public override string ToString()
    {
        return inner == 0 ? "EnumItem1" :
            inner == 2 ? "EnumItem2" :
            inner == 10 ? "EnumItem3" :
            inner.ToString();
    }
}

يمكن استخدام هذا الهيكل بنفس الطريقة التي يمكن أن يكون بها الهيكل ... بالطبع ، إذا حاولت أن تعكس النوع ، واتصل خاصية Isenum ، فسيتم إرجاعها.

دعونا نلقي نظرة على بعض مقارنة الاستخدام ، مع التعداد المكافئ:

enum MyEnum
{
    EnumItem1 = 0,
    EnumItem2 = 2,
    EnumItem3 = 10,
}

مقارنة الاستخدامات:

نسخة بنية:

var val = MyEnum.EnumItem1;
val = (MyEnum)50;
val = 0;
object obj = val;
bool isE = obj is MyEnum;
Enum en = val;

نسخة التعداد:

var valS = MyEnumS.EnumItem1;
valS = (MyEnumS)50;
//valS = 0; // cannot simulate this
object objS = valS;
bool isS = objS is MyEnumS;
IEnum enS = valS;

لا يمكن محاكاة بعض العمليات ، لكن كل هذا يوضح ما كنت أنوي قوله ... التعدادات مميزة ، نعم ... ما مقدار المميز؟ ليس كثيرا! =)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top