Bất cứ ai biết một giải pháp tốt cho việc thiếu một ràng buộc chung enum?


89

Những gì tôi muốn làm là một cái gì đó như thế này: Tôi có enums với các giá trị được gắn cờ kết hợp.

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
        return (input & matchTo) != 0;
    }
}

Vì vậy, sau đó tôi có thể làm:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

Thật không may, chung chung của C # trong đó các ràng buộc không có giới hạn enum, chỉ có lớp và cấu trúc. C # không xem enums là cấu trúc (mặc dù chúng là kiểu giá trị) nên tôi không thể thêm các kiểu mở rộng như thế này.

Có ai biết một cách giải quyết?


2
Keith: tải xuống phiên bản 0.0.0.2 của UnconstrainedMelody - Tôi đã triển khai HasAll và HasAny. Thưởng thức.
Jon Skeet 11/09/09

Ý bạn là gì khi “C # không xem enums là cấu trúc”? Bạn có thể sử dụng các kiểu enum làm tham số kiểu được giới hạn structở mức tốt.
Timwi

kiểm tra bài viết này tại đây: phương pháp codeproject.com/KB/cs/ExtendEnum.aspx 'IsValidEnumValue' hoặc 'IsFlagsEnumDefined' có thể là câu trả lời cho câu hỏi của bạn.
dmihailescu

1
Bỏ phiếu cho ý tưởng uservoice này , nếu bạn muốn một ngày nào đó bạn muốn thấy nó được tích hợp trong .net.
Matthieu

11
C # 7.3 giới thiệu các ràng buộc enum.
Marc Sigrist

Câu trả lời:


48

CHỈNH SỬA: Tính năng này hiện đã có trong phiên bản 0.0.0.2 của UnconstrainedMelody.

(Như đã yêu cầu trong bài đăng trên blog của tôi về các ràng buộc enum . Tôi đã bao gồm các thông tin cơ bản bên dưới vì lợi ích của một câu trả lời độc lập.)

Giải pháp tốt nhất là đợi tôi đưa nó vào UnconstrainedMelody 1 . Đây là một thư viện lấy mã C # với các ràng buộc "giả" chẳng hạn như

where T : struct, IEnumConstraint

và biến nó thành

where T : struct, System.Enum

thông qua một bước xây dựng sau.

Nó không phải là quá khó khăn để ghi IsSet... mặc dù phục vụ cho cả Int64dựa trên vàUInt64 cờ dựa trên cờ dựa trên có thể là một phần khó khăn. (Tôi ngửi thấy một số phương pháp trợ giúp sắp ra mắt, về cơ bản cho phép tôi xử lý mọi cờ enum như thể nó có một loại cơ sở là UInt64.)

Bạn muốn hành vi sẽ như thế nào nếu bạn gọi

tester.IsSet(MyFlags.A | MyFlags.C)

? Nó có nên kiểm tra rằng tất cả các cờ được chỉ định đã được đặt chưa? Đó sẽ là kỳ vọng của tôi.

Tôi sẽ cố gắng thực hiện việc này trên đường về nhà tối nay ... Tôi hy vọng sẽ hiểu nhanh về các phương pháp enum hữu ích để nhanh chóng đưa thư viện đạt tiêu chuẩn có thể sử dụng được, sau đó thư giãn một chút.

CHỈNH SỬA: Nhân tiện, tôi không chắc về IsSetcái tên. Các tùy chọn:

  • Bao gồm
  • Chứa đựng
  • HasFlag (hoặc HasFlags)
  • IsSet (chắc chắn là một tùy chọn)

Suy nghĩ hoan nghênh. Tôi chắc chắn rằng sẽ còn một thời gian nữa trước khi mọi thứ trở nên thành đá ...


1 hoặc gửi nó dưới dạng bản vá, tất nhiên ...


1

1
Hoặc thực sự đơn giản hơn HasAny () và HasAll ()
Keith

1
Vâng, tôi đồng ý rằng điều đó thậm chí còn tốt hơn. colors.HasAny(Colors.Red | Colors.Blue)trông giống như mã rất dễ đọc. =)
Blixt 11/09/09

1
Đúng, tôi cũng thích HasAny và HasAll. Sẽ đi với điều đó.
Jon Skeet 11/09/09

5
Kể từ C # 7.3 (phát hành tháng 5 năm 2018), có thể sử dụng ràng buộc where T : System.Enum. Điều này đã được viết ở nơi khác trong chủ đề; chỉ nghĩ rằng tôi sẽ lặp lại nó ở đây.
Jeppe Stig Nielsen


16

Darren, điều đó sẽ hoạt động nếu các kiểu là kiểu liệt kê cụ thể - để các kiểu liệt kê chung hoạt động, bạn phải chuyển chúng thành int (hoặc nhiều khả năng là uint) để thực hiện phép toán boolean:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

1
Và nếu bạn có một số lượng cờ vô lý, bạn có thể gọi GetTypeCode () trên các đối số và Convert.ToUint64 ()
Kit

Tuyệt vời, sự kết hợp giữa 'Enum' và Convert.ToUInt32tôi không tìm thấy ở đâu khác. AFAIK, giải pháp Pre-Net-4 tốt duy nhất cũng hoạt động trong VB. BTW, nếu matchTocó thể có nhiều bit cờ, sau đó thay thế != 0bằng == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
Lưu ý rằng Convert.ToUInt32được sử dụng với một enum sẽ sử dụng Convert.ToUInt32(object)quá tải, có nghĩa là CLR trước tiên sẽ đóng hộp các giá trị này trước khi chuyển đến ToUInt32phương thức. Trong hầu hết các trường hợp, điều này sẽ không thành vấn đề, nhưng thật tốt khi biết rằng bạn sẽ giữ GC khá bận rộn nếu bạn đang sử dụng một thứ như thế này để phân tích cú pháp hàng triệu enum mỗi giây.
Groo

10

Trên thực tế, nó là có thể, với một thủ thuật xấu xí. Tuy nhiên, nó không thể được sử dụng cho các phương pháp mở rộng.

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.IsSet<DateTimeKind>("Local")

Nếu bạn muốn, bạn có thể cung cấp Enums<Temp>một phương thức khởi tạo riêng và một lớp kế thừa trừu tượng lồng nhau công khai với Tempas Enum, để ngăn các phiên bản kế thừa cho các lớp không phải là enum.


8

Bạn có thể đạt được điều này bằng cách sử dụng IL Weaving và ExtraConstraints

Cho phép bạn viết mã này

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

Những gì được biên dịch

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}

6

Kể từ C # 7.3, bạn có thể sử dụng ràng buộc Enum trên các kiểu chung:

public static TEnum Parse<TEnum>(string value) where TEnum : Enum
{
    return (TEnum) Enum.Parse(typeof(TEnum), value);
}

Nếu bạn muốn sử dụng một enum Nullable, bạn phải rời khỏi ràng buộc struct orginial:

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}

4

Điều này không trả lời câu hỏi ban đầu, nhưng hiện có một phương pháp trong .NET 4 được gọi là Enum.HasFlag thực hiện những gì bạn đang cố gắng thực hiện trong ví dụ của mình


Được ủng hộ bởi vì tại thời điểm này, hầu hết mọi người nên sử dụng .NET 4 (hoặc cao hơn) và vì vậy họ nên sử dụng phương pháp này thay vì cố gắng hack nó cùng nhau.
CptRobby

Đã ủng hộ. Tuy nhiên giải pháp của họ sử dụng quyền anh của đối số flag. .NET 4.0 hiện đã được 5 năm tuổi.
Jeppe Stig Nielsen

3

Cách tôi làm là đặt một ràng buộc struct, sau đó kiểm tra xem T có phải là enum trong thời gian chạy hay không. Điều này không loại bỏ hoàn toàn vấn đề, nhưng nó làm giảm phần nào


7
nơi T: struct, IComparable, IFormattable, IConvertible - đây là gần nhất bạn có thể tới enum :)
Kit

1

Sử dụng mã gốc của bạn, bên trong phương thức, bạn cũng có thể sử dụng phản xạ để kiểm tra xem T có phải là enum không:

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}

2
Cảm ơn, nhưng điều đó biến vấn đề thời gian biên dịch (ràng buộc ở đâu) thành vấn đề thời gian chạy (ngoại lệ của bạn). Ngoài ra, bạn vẫn cần chuyển đổi các đầu vào thành int trước khi bạn có thể làm bất cứ điều gì với chúng.
Keith

1

Đây là một số mã mà tôi vừa tạo có vẻ hoạt động như bạn muốn mà không cần phải làm bất cứ điều gì quá điên rồ. Nó không bị giới hạn chỉ với các enum được đặt làm Cờ, nhưng luôn có thể có một kiểm tra được đưa vào nếu cần.

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}

0

nếu ai đó cần IsSet chung (có thể được cải thiện ngay lập tức) và hoặc chuỗi thành Enum onfly chuyển đổi (sử dụng EnumConstraint được trình bày bên dưới):

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet<TEnum>(this TEnum _this, TEnum flag)
      where TEnum : struct
    {
      return (((uint)Convert.ChangeType(_this, typeof(uint))) & ((uint)Convert.ChangeType(flag, typeof(uint)))) == ((uint)Convert.ChangeType(flag, typeof(uint)));
    }

    //public static TestClass ToTestClass(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestClass>(_this);
    //}

    //public static TestStruct ToTestStruct(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestStruct>(_this);
    //}

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse<TestEnum>(_this);
    }

    public static void TestAll()
    {
      TestEnum t1 = "e3".ToTestEnum();
      TestEnum t2 = "e2".ToTestEnum();
      TestEnum t3 = "non existing".ToTestEnum(); // default(TestEnum) for non existing 

      bool b1 = t3.IsSet(TestEnum.e1); // you can ommit type
      bool b2 = t3.IsSet<TestEnum>(TestEnum.e2); // you can specify explicite type

      TestStruct t;
      // #generates compile error (so no missuse)
      //bool b3 = t.IsSet<TestEnum>(TestEnum.e1);

    }

  }

Nếu ai đó vẫn cần ví dụ nóng để tạo ràng buộc mã hóa Enum:

using System;

/// <summary>
/// would be same as EnumConstraint_T&lt;Enum>Parse&lt;EnumType>("Normal"),
/// but writen like this it abuses constrain inheritence on System.Enum.
/// </summary>
public class EnumConstraint : EnumConstraint_T<Enum>
{

}

/// <summary>
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// </summary>
/// <typeparam name="TClass">should be System.Enum</typeparam>
public abstract class EnumConstraint_T<TClass>
  where TClass : class
{

  public static TEnum Parse<TEnum>(string value)
    where TEnum : TClass
  {
    return (TEnum)Enum.Parse(typeof(TEnum), value);
  }

  public static bool TryParse<TEnum>(string value, out TEnum evalue)
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    evalue = default(TEnum);
    return Enum.TryParse<TEnum>(value, out evalue);
  }

  public static TEnum TryParse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {    
    Enum.TryParse<TEnum>(value, out defaultValue);
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    TEnum result;
    if (Enum.TryParse<TEnum>(value, out result))
      return result;
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(ushort value)
  {
    return (TEnum)(object)value;
  }

  public static sbyte to_i1<TEnum>(TEnum value)
  {
    return (sbyte)(object)Convert.ChangeType(value, typeof(sbyte));
  }

  public static byte to_u1<TEnum>(TEnum value)
  {
    return (byte)(object)Convert.ChangeType(value, typeof(byte));
  }

  public static short to_i2<TEnum>(TEnum value)
  {
    return (short)(object)Convert.ChangeType(value, typeof(short));
  }

  public static ushort to_u2<TEnum>(TEnum value)
  {
    return (ushort)(object)Convert.ChangeType(value, typeof(ushort));
  }

  public static int to_i4<TEnum>(TEnum value)
  {
    return (int)(object)Convert.ChangeType(value, typeof(int));
  }

  public static uint to_u4<TEnum>(TEnum value)
  {
    return (uint)(object)Convert.ChangeType(value, typeof(uint));
  }

}

hy vọng điều này sẽ giúp ai đó.


0

Tôi chỉ muốn thêm Enum làm ràng buộc chung.

Mặc dù đây chỉ là một phương pháp trợ giúp nhỏ bằng cách sử dụng ExtraConstraints hơi quá nhiều đối với tôi.

Tôi quyết định chỉ tạo một structràng buộc và thêm kiểm tra thời gian chạy IsEnum. Để chuyển đổi một biến từ T sang Enum, tôi truyền nó thành đối tượng trước.

    public static Converter<T, string> CreateConverter<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter<T, string>(x => ((Enum)(object)x).GetEnumDescription());
    }
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.