【问题标题】:C#: Enums in InterfacesC#:接口中的枚举
【发布时间】:2010-06-30 20:39:22
【问题描述】:

我看到了几个与这个问题类似的主题,但没有一个能真正回答我想问的问题。

对于初学者来说,不幸的是,我正在使用现有的 API 代码,虽然可能有更好的方法来完成我所要求的工作,但我被锁定在做这件事的方式类似于它的方式,因为向后兼容性是不可协商的。

我有一个响应类,它当前包含一个错误代码的枚举和一个字符串描述。错误代码定义了一组相当不错且完整的响应,这些响应在语义上都与使用它们的操作非常耦合。

不幸的是,我现在必须为一组类似的 API 对象添加不同的工作流程,这将需要一个字符串描述,这很好,但还需要一个由一组完全不相关的错误代码组成的枚举错误代码。错误代码(以及对象模型的其他方面)将在许多相同的类中使用,因此最好有一个接口,以便我可以通过相同的框架运行对象。

这里的目的是制定一个合同,上面写着“我有一个错误代码,以及该错误代码的描述。”

但是,据我所知,没有办法将项目添加到诸如

之类的界面中
public interface IError
{
    enum ErrorCode;
    string Description;
}

也没有办法表达

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

以前有人遇到过这样的事情吗?

【问题讨论】:

  • 需要对该接口提供的ErrorCode进行哪些操作?如果您不需要访问特定于 Enum 的行为,则可以允许 ErrorCode 为任何类型而不会造成任何损害。

标签: c# generics interface enums


【解决方案1】:

是的,我遇到过这个问题。不是在这种特殊情况下,而是在其他 Stack Overflow 问题中,like this one。 (我不会投票关闭这个重复的,因为它略有不同。)

可以表达你的通用接口——只是不能在 C# 中。你可以在 IL 中毫无问题地做到这一点。我希望 C# 5 中的限制可以消除。据我所知,C# 编译器实际上完美地处理了约束。

如果您真的想选择此选项,您可以使用类似于Unconstrained Melody 中的代码,这是我拥有的一个库,它公开了具有这种难以生成的约束的各种方法。它有效地使用 IL 重写 - 它很粗糙,但它适用于 UM,并且可能也适用于您。不过,您可能希望将接口放入单独的程序集中,这会有些尴尬。

当然,你可以让你的界面只包含T : struct...这并不理想,但它至少会限制类型有点。只要您能确保它没有被滥用,那将相当有效。

【讨论】:

    【解决方案2】:

    正如 Jon Skeet 所提到的,基础 IL 支持将泛型限制为枚举,但是 C# 不允许您利用它。

    但是,F# 确实允许这种约束。此外,如果接口是在 F# 中定义的,则约束将在实现该接口的 C# 代码中强制执行。如果您愿意在您的解决方案中混合使用语言,那么这样的事情应该可以正常工作:

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

    如果您将它放在 F# 项目中并从 C# 项目中引用它,则实现该接口的 C# 代码将在任何尝试将其与非枚举类型一起使用时导致 C# 编译器错误。

    【讨论】:

    • 此解决方案对 C# 2.0 和 VS2005 的访问性如何?我猜……不是很?虽然听起来很甜美,但我不确定我能否利用这一点。
    • 间接地,你可以这样做。您必须安装免费的VS2008 ShellF# 2.0。然后,您可以在面向 CLR 2.0 的 F# 项目中定义您的接口,构建二进制文件,并从 VS2005 中的 C# 2 引用它们。如果您不经常更改界面,那么您几乎可以忽略刚刚安装的所有内容。
    • T 也需要被约束为值类型,否则 System.Enum 就可以了。
    • 顺便说一下,F# 允许您在枚举类型约束中更加具体。如果您只想允许 Int32 派生的枚举而不是基于字节或 Int64 的枚举,则可以添加此约束:and 'T : enum&lt;int&gt;
    【解决方案3】:

    您可以采用稍微不同的方法:

    public interface IError
    {
        Enum ErrorCode;
        string Description;
    }
    

    System.Enum 是所有枚举的基类,因此应该可以处理它,但 它远没有表现力。

    这里的正确方法是构建您自己的枚举类和一个基础枚举类。例如,

    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;
    }
    

    现在不要使用自己的错误枚举,而是编写自己的 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
    

    现在你的主要课程:

    public class ReportError : IError
    {
        // implementation
    }
    
    public class DataError : IError
    {
        // implementation
    }
    

    否则,

    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
    

    要获得具有枚举约束的丑陋方式,请参阅Anyone know a good workaround for the lack of an enum generic constraint?

    【讨论】:

      【解决方案4】:

      无法写public interface IError&lt;T&gt; where T: enum 是我们多年来一直抱怨的事情。到目前为止,这方面没有任何进展。

      我通常最终会写public interface IError&lt;T&gt; 并为实现者留下一条说明 T 必须是枚举。

      【讨论】:

        【解决方案5】:

        如果我了解您想要做什么,那么是的,没有办法定义一个接口,其中包含一个非特定枚举作为其成员之一。您的第二个示例很接近,但您仅限于将T 的类型限制为struct,这将允许任何值类型。那时,您只需要依靠正确使用接口的知识来让人们知道T 的预期类型应该是一个枚举。您可以通过将T 定义为TEnumTErrorValue 来使其更清晰:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-07-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多