2010-06-30 39 views
17

Tôi đã nhìn thấy một vài chủ đề tương tự cho câu hỏi này, nhưng không ai trong số họ thực sự trả lời câu hỏi tôi muốn hỏi. Đối với người mới bắt đầu, thật không may là tôi đang làm việc với mã API hiện tại thật đáng buồn, trong khi có cách tốt hơn để làm những gì tôi hỏi, tôi bị nhốt để làm điều tương tự như vậy khả năng tương thích ngược không thể thương lượng được.C#: Enums trong giao diện

Tôi có lớp phản hồi hiện chứa enum cho mã lỗi và mô tả chuỗi. Các mã lỗi xác định một tập hợp khá tốt đẹp và đầy đủ các câu trả lời được tất cả rất semantically cùng với các hoạt động mà họ đang sử dụng.

Thật không may, bây giờ tôi phải thêm quy trình làm việc khác cho một tập hợp các đối tượng API tương tự, và điều này sẽ yêu cầu mô tả chuỗi, điều này là tốt, nhưng cũng là mã lỗi enum bao gồm một bộ mã lỗi hoàn toàn không liên quan. Các mã lỗi (và các khía cạnh khác của mô hình đối tượng) sẽ được sử dụng trong rất nhiều các lớp giống nhau, vì vậy nó sẽ là tốt đẹp để có được một giao diện đi để tôi có thể chạy các đối tượng thông qua cùng một khuôn khổ.

Mục đích ở đây là tạo hợp đồng cho biết "Tôi có mã lỗi và mô tả mã lỗi đó".

Tuy nhiên, theo như tôi biết không có cách nào để thêm một mục vào một giao diện như

public interface IError 
{ 
    enum ErrorCode; 
    string Description; 
} 

cũng không phải là có một cách để thể hiện

public interface IError<T> where T: enum 
{ 
    T ErrorCode; 
    string Description; 
} 

Bất cứ ai từng chạy lên chống lại một cái gì đó như thế này trước đây?

+0

gì hoạt động, bạn sẽ cần thế nào để thực hiện trên ErrorCode được cung cấp bởi giao diện này? Nếu bạn không cần truy cập vào hành vi cụ thể của Enum, bạn có thể cho phép ErrorCode là bất kỳ loại nào không có hại. –

Trả lời

13

Có, tôi đã chống lại điều này. Không phải trong tình huống cụ thể này, nhưng trong các câu hỏi về Stack Overflow khác, like this one. (Tôi không bỏ phiếu để đóng một bản sao này như một bản sao, vì nó hơi khác nhau.)

có thể thể hiện giao diện chung của bạn - không có trong C#. Bạn có thể làm điều đó trong IL mà không có vấn đề gì. Tôi hy vọng giới hạn có thể được gỡ bỏ trong C# 5. Trình biên dịch C# thực sự xử lý ràng buộc một cách hoàn hảo một cách chính xác, theo như tôi đã thấy.

Nếu bạn thực sự muốn sử dụng tùy chọn này, bạn có thể sử dụng mã tương tự như trong Unconstrained Melody, một thư viện tôi đã trưng bày các phương pháp khác nhau với khó khăn khó khăn trong sản xuất này. Nó sử dụng IL viết lại, hiệu quả - đó là thô, nhưng nó hoạt động cho UM và có lẽ sẽ làm việc cho bạn quá. Bạn có thể muốn đưa giao diện vào một hội đồng riêng biệt mặc dù, đó sẽ là hơi khó xử.

Tất nhiên, bạn có thể làm cho giao diện của mình chỉ có T : struct thay vì ... nó sẽ không lý tưởng, nhưng ít nhất nó sẽ hạn chế loại phần nào là. Vì vậy, miễn là bạn có thể chắc chắn rằng nó không bị lạm dụng, điều đó sẽ hoạt động khá tốt.

1

Không có khả năng viết public interface IError<T> where T: enum là điều chúng tôi đã phàn nàn trong nhiều năm. Cho đến nay đã không có tiến bộ về điều đó.

Tôi thường kết thúc bằng văn bản public interface IError<T> và để lại ghi chú cho người triển khai rằng T phải là enum.

1

Nếu tôi hiểu những gì bạn muốn làm, sau đó có, không có cách nào để xác định một giao diện có chứa như là một trong những thành viên của nó là một enum không cụ thể.Ví dụ thứ hai của bạn là gần, nhưng bạn bị giới hạn để hạn chế loại T thành một số struct, điều này sẽ cho phép bất kỳ loại giá trị nào. Tại thời điểm đó, bạn chỉ cần dựa vào kiến ​​thức về giao diện sử dụng thích hợp để cho mọi người biết loại mong đợi của T phải là một enum. Bạn có khả năng có thể làm cho nó rõ ràng hơn một chút bằng cách định nghĩa T như TEnum hoặc TErrorValue:

public interface IError<TEnum> where T: struct 
{ 
    T ErrorCode; 
    string Description; 
} 
8

Như Jon Skeet đã đề cập, IL cơ sở hỗ trợ hạn chế Generics là sự đếm, tuy nhiên C# không cho phép bạn tận dụng lợi thế của nó.

F # cho phép loại ràng buộc này, tuy nhiên. Hơn nữa, nếu giao diện được định nghĩa trong F #, ràng buộc sẽ được thực thi trong mã C# thực hiện giao diện. Nếu bạn sẵn sàng để pha trộn ngôn ngữ trong giải pháp của bạn, một cái gì đó như thế này nên chỉ làm việc tốt:

type IError<'T when 'T :> System.Enum and 'T : struct> = 
    abstract member Error : 'T 
    abstract member Description : string 

Nếu bạn đặt này trong một # dự án F và tham khảo nó từ dự án # C, mã # C của bạn mà thực hiện giao diện sẽ gây ra lỗi trình biên dịch C# trên bất kỳ nỗ lực nào để sử dụng nó với loại không phải là enum.

+0

Giải pháp này có thể truy cập tới C# 2.0 và VS2005 như thế nào? Tôi đoán ... không phải là rất? Trong khi nghe có vẻ ngọt ngào, tôi không chắc mình sẽ có thể tận dụng lợi thế. – bwerks

+1

Gián tiếp, bạn có thể làm điều này. Bạn sẽ phải cài đặt [VS2008 Shell] miễn phí (http://www.microsoft.com/downloads/details.aspx?FamilyId=40646580-97FA-4698-B65F-620D4B4B1ED7&displaylang=en) và [F # 2.0] (http : //www.microsoft.com/downloads/details.aspx? FamilyID = 444005fb-e627-4feb-b51d-13d6a3b4b8ed & displaylang = vi). Sau đó, bạn có thể định nghĩa các giao diện của mình trong dự án F # nhắm mục tiêu CLR 2.0, xây dựng các tệp nhị phân và tham chiếu chúng từ C# 2 trong VS2005. Nếu bạn không thường xuyên thay đổi giao diện của mình, bạn có thể bỏ qua mọi thứ bạn vừa cài đặt. –

+0

T cũng cần phải được hạn chế để trở thành một loại giá trị, nếu không System.Enum sẽ không sao. –

3

Bạn có thể đi với cách tiếp cận của bạn theo một cách hơi khác nhau:

public interface IError 
{ 
    Enum ErrorCode; 
    string Description; 
} 

System.Enum là lớp cơ sở của tất cả các sự đếm của bạn, do đó nên xử lý nó, nhưng nó xa là biểu cảm.

Cách tiếp cận đúng ở đây là xây dựng các lớp enum của riêng bạn và một lớp enum cơ sở cho nó. Ví dụ:

public class ErrorFlag // base enum class 
{ 
    int value; 

    ErrorFlag() 
    { 

    } 

    public static implicit operator ErrorFlag(int i) 
    { 
     return new ErrorFlag { value = i }; 
    } 

    public bool Equals(ErrorFlag other) 
    { 
     if (ReferenceEquals(this, other)) 
      return true; 

     if (ReferenceEquals(null, other)) 
      return false; 

     return value == other.value; 
    } 

    public override bool Equals(object obj) 
    { 
     return Equals(obj as ErrorFlag); 
    } 

    public static bool operator ==(ErrorFlag lhs, ErrorFlag rhs) 
    { 
     if (ReferenceEquals(lhs, null)) 
      return ReferenceEquals(rhs, null); 

     return lhs.Equals(rhs); 
    } 

    public static bool operator !=(ErrorFlag lhs, ErrorFlag rhs) 
    { 
     return !(lhs == rhs); 
    } 

    public override int GetHashCode() 
    { 
     return value; 
    } 

    public override string ToString() 
    { 
     return value.ToString(); 
    } 
} 

public interface IError 
{ 
    ErrorFlag ErrorCode; 
    string Description; 
} 

Bây giờ thay vì có enums lỗi của riêng bạn, hãy viết các lớp riêng của bạn ErrorFlag.

public sealed class ReportErrorFlag : ErrorFlag 
{ 
    //basically your enum values 
    public static readonly ErrorFlag Report1 = 1; 
    public static readonly ErrorFlag Report2 = 2; 
    public static readonly ErrorFlag Report3 = 3; 

    ReportErrorFlag() 
    { 

    } 
} 

public sealed class DataErrorFlag : ErrorFlag 
{ 
    //basically your enum values 
    public static readonly ErrorFlag Data1 = 1; 
    public static readonly ErrorFlag Data2 = 2; 
    public static readonly ErrorFlag Data3 = 3; 

    DataErrorFlag() 
    { 

    } 
} 

// etc 

Bây giờ các lớp học chính của bạn:

public class ReportError : IError 
{ 
    // implementation 
} 

public class DataError : IError 
{ 
    // implementation 
} 

Hoặc cách khác,

public class ErrorFlag // base enum class 
{ 
    internal int value { get; set; } 

    public bool Equals(ErrorFlag other) 
    { 
     if (ReferenceEquals(this, other)) 
      return true; 

     if (ReferenceEquals(null, other)) 
      return false; 

     return value == other.value; 
    } 

    public override bool Equals(object obj) 
    { 
     return Equals(obj as ErrorFlag); 
    } 

    public static bool operator ==(ErrorFlag lhs, ErrorFlag rhs) 
    { 
     if (ReferenceEquals(lhs, null)) 
      return ReferenceEquals(rhs, null); 

     return lhs.Equals(rhs); 
    } 

    public static bool operator !=(ErrorFlag lhs, ErrorFlag rhs) 
    { 
     return !(lhs == rhs); 
    } 

    public override int GetHashCode() 
    { 
     return value; 
    } 

    public override string ToString() 
    { 
     return value.ToString(); 
    } 
} 

public interface IError<T> where T : ErrorFlag 
{ 
    T ErrorCode { get; set; } 
    string Description { get; set; } 
} 

//enum classes 
public sealed class ReportErrorFlag : ErrorFlag 
{ 
    //basically your enum values 
    public static readonly ReportErrorFlag Report1 = new ReportErrorFlag { value = 1 }; 
    public static readonly ReportErrorFlag Report2 = new ReportErrorFlag { value = 2 }; 
    public static readonly ReportErrorFlag Report3 = new ReportErrorFlag { value = 3 }; 

    ReportErrorFlag() 
    { 

    } 
} 

public sealed class DataErrorFlag : ErrorFlag 
{ 
    //basically your enum values 
    public static readonly DataErrorFlag Data1 = new DataErrorFlag { value = 1 }; 
    public static readonly DataErrorFlag Data2 = new DataErrorFlag { value = 2 }; 
    public static readonly DataErrorFlag Data3 = new DataErrorFlag { value = 3 }; 

    DataErrorFlag() 
    { 

    } 
} 

//implement the rest 

Để có cách xấu xí của việc có trở ngại enum, xem Anyone know a good workaround for the lack of an enum generic constraint?