هل هناك حل بديل لقيود النوع العام لـ Enum "فئة خاصة" في C# 3.0؟[ينسخ]

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

سؤال

هذا السؤال لديه بالفعل إجابة هنا:

تحديث: انظر الجزء السفلي من هذا السؤال للحصول على حل بديل لـ C#.

أهلاً،

خذ بعين الاعتبار طريقة التمديد التالية:

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}

سيؤدي هذا، كما تعلم، إلى حدوث خطأ في وقت الترجمة، نظرًا لأنه لا يُسمح عادةً للفصل بالوراثة منه System.Enum.المشكلة هي أن أي تعداد محدد باستخدام enum الكلمة الأساسية ترث في الواقع من System.Enum, ، لذا فإن الكود أعلاه سيكون الطريقة المثالية لتقييد طريقة الامتداد بالتعدادات فقط.

الآن الحل الواضح هنا هو الاستخدام Enum بدلاً من T, ، لكنك تفقد بعد ذلك فوائد الأنواع العامة:

MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);

قد يؤدي التعليمة البرمجية أعلاه إلى حدوث خطأ في وقت الترجمة باستخدام أنواع عامة، في حين أنه يمكنه فقط إلقاء خطأ في وقت التشغيل باستخدام Enum اكتب (إذا قمت بتنفيذه للقيام بذلك.)

هل هناك أي خيارات للمترجم يمكن استخدامها لإيقاف تشغيل التحقق من القيد، أم أن هناك طريقة أخرى رائعة للقيام بذلك؟

قبل أن يتم اقتراحه، أود أن أقول أنني لن أستخدمه where T : struct أو شيء من هذا القبيل، منذ ذلك الحين ستتمكن من القيام بأشياء غريبة مثل 123.HasFlags(456).

أنا في حيرة من أمري لماذا يوجد هذا الخطأ على الإطلاق ...إنها نفس المشكلة التي ستستخدمها where T : System.Object, ولكن لهذا لديك where T : class...لماذا لا يوجد where T : enum?

الحل C#

بدأ جون سكيت العمل في مكتبة تجمع الفصول الدراسية مع قيود على IEnumConstraint, ، والذي يتم استبداله بعد ذلك بـ System.Enum ما بعد البناء.أعتقد أن هذا هو أقرب ما يمكن حله حول هذه المشكلة في هذا الوقت.

يرى:

إذا كان هذا الحل البديل غير ممكن، فسيتعين عليك كتابة مكتبتك كرمز C++/CLI، وهو ما لا يحد مما يمكن استخدامه لقيود النوع العامة (انظر الكود الموجود في إجابتي أدناه.)

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

المحلول

يحرر:تتوفر الآن مكتبة تدعم ذلك عبر ildasm/ilasm: ميلودي غير مقيد.


لقد قال أعضاء فريق C# سابقًا أنهم سيفعلون ذلك يحب لتكون قادرة على دعم where T : Enum و where T : Delegate, ، لكنها لم تكن أبدًا أولوية عالية بما فيه الكفاية.(لست متأكدًا من السبب وراء وجود التقييد في المقام الأول، باعتراف الجميع...)

الحل الأكثر عملية في C# هو:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    // ...
}

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

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    return EnumHelper<T>.HasFlags(value, flags);
}

private class EnumHelper<T> where T : struct
{
    static EnumHelper()
    {
        if (!typeof(Enum).IsAssignableFrom(typeof(T))
        {
            throw new InvalidOperationException(); // Or something similar
        }
    }

    internal static HasFlags(T value, T flags)
    {
        ...
    }
}

كما ذكر Greco، يمكنك كتابة الطريقة في C++/CLI ثم الرجوع إلى مكتبة الفصل من C# كخيار آخر.

نصائح أخرى

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

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.Parse<DateTimeKind>("Local")

إذا كنت تريد، يمكنك أن تعطي Enums<Temp> منشئ خاص وفئة مجردة متداخلة عامة مع Temp مثل Enum, ، لمنع الإصدارات الموروثة لغير التعدادات.

لم أستطع مقاومة تجربة الحل البديل لـ C++، ومنذ أن بدأت العمل به، قررت أن أشاركه مع البقية!

إليك رمز C++ (لغة C++ الخاصة بي صدئة للغاية، لذا يرجى الإشارة إلى أي أخطاء، وخاصة كيفية تعريف الوسائط):

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace Blixt
{
namespace Utilities
{
    [Extension]
    public ref class EnumUtility abstract sealed
    {
    public:
        generic <typename T> where T : value class, Enum
        [Extension]
        static bool HasFlags(T value, T flags)
        {
            __int64 mask = Convert::ToInt64(flags);
            return (Convert::ToInt64(value) & mask) == mask;
        }
    };
}
}

ورمز C# للاختبار (تطبيق وحدة التحكم):

using System;
using Blixt.Utilities;

namespace Blixt.Playground
{
    [Flags]
    public enum Colors : byte
    {
        Black = 0,
        Red = 1,
        Green = 2,
        Blue = 4
    }

    [Flags]
    public enum Tastes : byte
    {
        Nothing = 0,
        Sour = 1,
        Sweet = 2,
        Bitter = 4,
        Salty = 8
    }

    class Program
    {
        static void Main(string[] args)
        {
            Colors c = Colors.Blue | Colors.Red;
            Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
            Console.WriteLine("Blue?           {0}", c.HasFlags(Colors.Blue));
            Console.WriteLine("Green?          {0}", c.HasFlags(Colors.Green));
            Console.WriteLine("Red and blue?   {0}", c.HasFlags(Colors.Red | Colors.Blue));

            // Compilation error:
            //Console.WriteLine("Sour?           {0}", c.HasFlags(Tastes.Sour));

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}

يمكنك تحقيق ذلك باستخدام IL Weaving و القيود الإضافية

يسمح لك بكتابة هذا الرمز

public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
    // ...
} 

ما يتم تجميعه

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
} 
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top