我有一个类,它使用空接口作为“标记接口”,如下所示:
namespace MyNameSpace
{
public interface IMessage
{
//nothing in common here...
}
public class MyMessage : IMessage
{
public void SendMyMessage()
{
//Do something here
}
}
}
我在其他一些帖子中读到了,但在MSDN(http://msdn.microsoft.com/en-us/library/ms182128.aspx)上也应该避免这种情况,你应该使用自定义属性而不是这个空接口。所以,我可以像这样重构我的代码:
namespace MyNameSpace
{
public class MessageAttribute : Attribute
{
//nothing in common here...
}
[MessageAttribute]
public class MyMessage
{
public void SendMyMessage()
{
//Do something here
}
}
}
一切正常,但主要问题是:
当我在程序的其他地方使用泛型方法时,例如:
public IEnumerable<T> GetAll<T>() where T : IMessage
{
//Return all IMessage things here
}
在上面的函数中,我需要在T
上添加一些泛型类型约束,因此只允许IMessages
。
如何在使用自定义属性而不是空接口时完成此操作?
这是否证明使用空接口是正确的?或者我应该使用空的抽象类Message
(而不是接口IMessage
,因为MyMessage
实际上是Message
)。
非常好奇你对此的看法。
答案 0 :(得分:7)
使用自定义属性而不是空接口时,我怎么能做到这一点?这是否证明使用空接口是合理的?
你不能 - 至少,不能在编译时。您当然可以在运行时检查属性。
或者我应该使用空的抽象类消息
基类比接口更具限制性;我个人会施加最少的开销。但我想知道即使是人工界面(它什么都不给你)本身就是开销,另一种选择就是:不要提出任何这样的要求。让人们添加一个接口只是为了一个方法编译并不会给你带来太多的东西,而不仅仅是一个没有约束的泛型方法。
答案 1 :(得分:4)
作为C# currently does not offer any attribute-based generic constraint,除了使用标记界面之外别无选择。
有趣的是,documentation page on CA1040声明规则的以下例外:
在接口用于在编译时识别一组类型时,可以安全地禁止来自此规则的警告。
在评估通用约束时,确定“编译时的一组类型”似乎正是所需要的,我现在想知道该文档页面的作者是否完全考虑到了这一点。
答案 2 :(得分:2)
Microsoft的文档中的一些建议说&#34;通常更喜欢X而不是Y&#34;当更有帮助的是要注意有些情况下每个都是正确的而另一个是正确的简直错了。
关于属性,标记接口和复合接口之间的选择,每个语义的语义将指示它何时适合或不适合。在给定情况下最需要什么语义的问题可能是判断调用,但如果需要特定语义,则属性和接口之间的选择通常不判断调用。
任何实现公共接口的类都代表自己及其后代向全世界做出承诺,任何对该类对象的引用都将是对实现的东西的引用界面。这样做,一个类将使任何派生类都不可能避免做出同样的承诺。相比之下,具有承诺某些特征的属性的未密封类仅承诺该特征将应用于该类的实例。无法保证它将应用于由基类类型的引用标识的派生类实例的实例。
如果希望指定某些特征将适用于所有派生类型,则应使用某种形式的界面表示该特征。如果希望允许派生类单独决定是否宣传基类所宣传的特征,则该特征应表示为属性。
请注意,即使决定使用界面来表达特征,也并不意味着应该使用空标记界面。如果特征仅与也实现某个其他接口的对象一起使用,则从一个或多个其他接口继承的复合接口可能比必须在通用约束中与其他接口组合的标记接口更有用。除其他外,如果一个人有一个空的标记界面,例如IIsImmutable
以及包含ImmutableList<T>
IIsImmutable
的一个或多个类,同时实现IEnumerable<T>
和IIsImmutable
,可以将实现IEnumerable<T>
和IIsImmutable
的任何类型的引用传递给参数限制为IEnumerable<T>
和Object
的方法但是给定一个类型未知的IImmutableEnumerable<out T> : IEnumerable<T>
,除了它可以转换为两种接口类型之外,没有可以安全地转换这种对象的类型,这将满足两个约束。如果不是使用标记接口,而是定义了接口IEnumerable<T>
,那么实现IImmutableEnumerable<T>
并想要宣传其不变性的对象可以转换为ISelf<out T> { T Value {get;}}
。
在某些情况下,拥有接口T
然后让标记接口接受泛型类型参数ISelf<T>
并从IEnumerable<T>
继承可能很有用。然后需要IImmutable<IEnumerable<T>>
的不可变实现的代码可以采用类型ISelf<itsOwnType>
的参数。对实现thing
的任何类对象的引用可以自由地转换为它实现的任何标记接口组合。该方法的唯一困难是使用IImmutable<IEnumerable<T>>
类型IEnumerable<T>
作为thing.Value.GetEnumerator()
将需要使用例如thing.Value
,虽然可能期望thing
和thing.Value
应该是同一个对象(暗示thing
应该实现{{1}}所做的所有接口类型系统中的任何内容都不会强制执行。
答案 3 :(得分:0)
当接口包含此接口需要此方法的SendMyMessage时,这没有错。
namespace MyNameSpace
{
public interface IMessage
{
// If your architecture need this method and this method belong to this interface then why not ?!
SendMyMessage();
}
public class MyMessage : IMessage
{
public void SendMyMessage()
{
//Do something here
}
}
}
例如,您可以这样做:
public class MessageRepository<T> where T : IMessage
{
public IEnumerable<T> GetAll()
{
throw new NotImplementedException();
}
}
或:
public class MessageRepository
{
public IEnumerable<T> GetAll<T>() where T : IMessage
{
throw new NotImplementedException();
}
}