C#:接口中的枚举

时间:2010-06-30 20:39:23

标签: c# generics interface enums

我在这个问题上看到了几个类似的线索,但是他们都没有真正回答我想问的问题。

对于初学者来说,不幸的是我正在使用现有的API代码,所以很遗憾,虽然可能有更好的方法来做我要求的事情,但我仍然会以类似的方式执行它,因为它是向后的兼容性是不可协商的。

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

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

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

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

public interface IError
{
    enum ErrorCode;
    string Description;
}

也没有办法表达

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

之前每个人都遇到过这样的事情吗?

5 个答案:

答案 0 :(得分:14)

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

可以表达你的通用界面 - 只是不在C#中。你可以在IL中做到没有问题。我希望在C#5中可以删除限制。就我所见,C#编译器实际上正确地处理了约束。

如果你真的想作为一个选项,你可以使用类似于Unconstrained Melody中的代码,我已经得到了一个库,它使用这个难以生成的约束公开了各种方法。它有效地使用IL重写 - 它很粗糙,但它适用于UM,也可能适合你。你可能想把接口放到一个单独的程序集中,这有点尴尬。

当然,你可以让你的界面只有T : struct而不是...它不是理想的,但它至少会限制类型。只要你能确保它没有被滥用,那就行得相当好。

答案 1 :(得分:8)

正如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#编译器错误,无法尝试将其与非枚举类型一起使用。

答案 2 :(得分: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?

答案 3 :(得分:1)

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

我通常最后写public interface IError<T>并为实现者留下一个注释,即T必须是枚举。

答案 4 :(得分:1)

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

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