我在这个问题上看到了几个类似的线索,但是他们都没有真正回答我想问的问题。
对于初学者来说,不幸的是我正在使用现有的API代码,所以很遗憾,虽然可能有更好的方法来做我要求的事情,但我仍然会以类似的方式执行它,因为它是向后的兼容性是不可协商的。
我有一个响应类,当前包含错误代码和字符串描述的枚举。错误代码定义了一组相当不错且完整的响应,这些响应都非常语义地耦合到使用它们的操作。
不幸的是,我现在必须为一组类似的API对象添加不同的工作流程,这将需要一个字符串描述,这很好,但也是一个包含一组完全不相关的错误代码的枚举错误代码。错误代码(以及对象模型的其他方面)将在许多相同的类中使用,因此最好让接口运行,以便我可以通过相同的框架运行对象。
这里的目的是签订一份合同,上面写着“我有一个错误代码,以及该错误代码的描述。”
但是,据我所知,没有办法将项目添加到界面,例如
public interface IError
{
enum ErrorCode;
string Description;
}
也没有办法表达
public interface IError<T> where T: enum
{
T ErrorCode;
string Description;
}
之前每个人都遇到过这样的事情吗?
答案 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
定义为TEnum
或TErrorValue
,您可以更清楚地说明一点:
public interface IError<TEnum> where T: struct
{
T ErrorCode;
string Description;
}