你写过的最好(最有用)的接口是什么?

4

标题基本上已经说明了问题。您编写的接口使您感到自豪并且经常使用。我猜编写 IEnumerable<T> 和尤其是 IQueryable<T> 的人在创建它们后会有一种良好的感觉。


1
这听起来像是一个很好的社区维基状态的候选人。 - Jay Bazuzi
6个回答

5
严格的接口?还是只是API?我对通用操作员的工作方式感到满意(在此处可用) - 我经常看到人们询问如何使用泛型运算符,所以我很高兴它是众多人的便利答案。在C# 4.0中可能会更容易,但我非常怀疑它是否像DLR-tree/dynamic那样快速 - 它有一个开销。
我也很高兴它在Jon提到的Push LINQ中很有帮助 ;-p

像Marc一样,我不确定它是否适用于这个问题,但我可以肯定地确认他的通用运算符很棒 :) - Jon Skeet

3

我对Push LINQ的界面设计感到满意。它是一个非常简单的界面,但是你可以用它做各种有趣的事情。下面是定义(从记忆中得出,但至少会非常接近):

public interface IDataProducer<T>
{
    event Action<T> DataProduced;
    event Action EndOfData;
}

基本上,它允许观察者“监听”数据流,而不是以IEnumerable的方式“拉取”数据。
有三个值得注意的点:
1. 名称很糟糕。我想重新命名它,并且已经收到了一些好的反馈,但名称还没有确定。
2. 事件处理程序不遵循标准的.NET约定,即具有发送器/事件参数。在这种情况下,这样做确实没有多大意义。
3. 事件的多播特性使其非常适合计算多个聚合等。所有这些都是免费的。

方便地,它可以与 C# 4.0 的 variance 一起愉快地工作(我一段时间以前在 CTP 上检查了一下 ;-p)。幸好我们没有在接口中犯错并放入“do a push”方法。 - Marc Gravell
1
你有没有考虑过不使用EventHandler委托所带来的安全隐患?例如,如果你的代码具有完全信任,则部分受信任的代码可以将Environment.FailFast注册为IDataProducer<string>.DataProduced的处理程序,并在完全信任的上下文中运行。 - Greg Beech
我之前没有考虑过,但如果它们在完全信任的上下文中运行,它们不能自己调用它吗? - Jon Skeet
@Greg:经过一夜的思考,我想我可能知道你的意思了。如果它成为一个问题,我会改变它,但现在还好。 - Jon Skeet

1
我猜写了IEnumerable的那些人...在创建它后应该感觉很不错。
但我并不那么确定。就接口而言,IEnumerable相当失败。这可以从其泛型版本IEnumerable 实际上提供了完全不同的接口这一事实得到证明(嗯,“不同”不是一个好词;基本上,它增加了一种可丢弃性,并减少了完全无用的Reset方法;然而,当然仍然存在此方法)。
另外,类似的具有类似结构的语言(例如Java)具有更好、更可扩展的构造来满足相同的需求(及更多)。例如,Java的迭代器可以扩展为双向迭代器或允许修改访问(而IEnumerable始终是只读的)。
编辑:由于评论中有如此多的争议,让我澄清。我并不是说IEnumerable(或IEnumerator)是不良接口。它们是适当的。但是,它们可以做得更好。如果没有其他办法,那么至少Reset方法似乎是杂物。
Terjet说他“一直在使用” - 这正是我的观点!IEnumerable是整个.NET框架的关键接口。它无处不在。没有它就没有.NET。因此,要求一个“完美”的接口是否太过分了?
“足够”的只是失败的另一个词。

我也不同意可扩展性的观点 - 许多迭代器根本不适合可写或双向,一旦您无法真正依赖于接口被完全实现,那些半可选成员就变得不那么有用了。(即使在IEnumerator中的Reset,在我看来也是一个错误。) - Jon Skeet
重置(Reset())为什么是一个糟糕的想法? - Sandeep Datta
许多事情是可能的(例如迭代器块),因为 IEnumerable 是如此有限。这并不是说其他更复杂的 API 没有用处 - IList<T> 等等 - 但是在这些更复杂的接口上实现 LINQ 和迭代器块等功能将会很困难。 - Jon Skeet
因此,应该有不同的接口来模拟更强大的迭代器 - 这很好。只是不要强迫我抛出异常,因为我实际上无法实现所有功能 - 拥有一个更简单的接口(如IEnumerable)会更好。简而言之,我非常不同意将IEnumerable称为失败。 - Jon Skeet
你会从IEnumerable+IEnumerator中删除什么来获得简单性呢?大多数人会删除Reset,但是其他所有内容肯定都是迭代概念所必需的。它最小限度地捕获了前向迭代器的概念。 - Daniel Earwicker
显示剩余15条评论

1

为了进行MVP模式的工作,我有一些基本的框架接口:

public interface IValidatable {
  bool IsValid { get; set; }
  void ShowValidationFailureMessage(string message);
}

public interface ISubmitable {
  event EventHandler Submit;
  void ShowSubmitFailureMessage(string message);
  void ShowSubmitSuccessMessage(string message);
}

public interface ICancelable {
  event EventHandler Cancel;
}

通过这3个接口,我可以编写具有以下通用操作的Presenter(基本涵盖了所有表单操作)。例如:

public interface ILogin : IValidatable, ISubmitable, ICancelable {
  string Username { get; set; }
  string Password { get; set; }
}

接下来,您可以创建一个演示文稿并将其桩出。


0

我正在开发一个验证系统,计划很快发布给社区使用。它本质上是规范模式的实现。

核心接口的设计是以功能为导向的:

public interface ICheckable<T>
{
    CheckResult Apply(T target);
}

CheckResult 是一个表示三状态值的 struct: Passed, Failed, 和 Ignored。已经设置了所有的转换和运算符重载,以将其视为 Boolean 值。

这使验证器可以表达“我没有意见”,而不是返回一个误导性的 true 值(比如 RangeValidator 表示空字段是有效的,因此它与 RequiredFieldValidator 相互协作)。

组合自然而然地完成,并使用静态类类似于 Linq。每个点进入检查是下一个操作的隐式 And

public static ICheckable<T> Add<T>(this ICheckable<T> check, ICheckable<T> otherCheck)
{
    return new Check<T>(t => check.Apply(t) && otherCheck.Apply(t));
}

public static ICheckable<T> Either<T>(this ICheckable<T> check, ICheckable<T> firstCheck, ICheckable<T> secondCheck)
{
    return check.Add(t => firstCheck.Apply(t) || secondCheck.Apply(t));
}

public static ICheckable<T> Not<T>(this ICheckable<T> check, ICheckable<T> negatedCheck)
{
    return check.Add(t => !negatedCheck.Apply(t));
}

扩展方法非常好用:

public static ICheckable<int> Percentage(this ICheckable<int> check)
{
    return check.Add(n => n >= 0 && n <= 100);
}

public static ICheckable<T> GreaterThanOrEqualTo<T>(this ICheckable<T> check, T value) where T : IComparable<T>
{
    return check.Add(t => t.CompareTo(value) >= 0);
}

public static ICheckable<T> LessThanOrEqualTo<T>(this ICheckable<T> check, T value) where T : IComparable<T>
{
    return check.Add(t => t.CompareTo(value) <= 0);
}

public static ICheckable<T> Range<T>(this ICheckable<T> check, T minimum, T maximum) where T : IComparable<T>
{
    return check.GreaterThanOrEqualTo(minimum).LessThanOrEqualTo(maximum);
}

// RangeExcludeMinimum
// RangeExcludeMaximum
// RangeExclusive

每个操作都包括重载,其中包含一个用于构建检查的 lambda 函数:

public static ICheckable<T> Add<T>(this ICheckable<T> check, Func<ICheckable<T>, ICheckable<T>> makeCheck)
{
    return check.Add(makeCheck(new IgnoredCheck<T>()));
}

所以你可以像这样编写语法:

ICheckable<int> check;

check.Add(i => i.Percentage().GreaterThan(50).Even());

为什么不把Apply命名为Check呢?根据结果和接口名称,我会这样期望。 - Jon Skeet
哈哈,好发现 :-) 这个接口最初叫做 ICheck<T>,但我重命名它以表示它的延迟性质。我可能会采纳这个建议。 - Bryan Watts

0

这是一个ActionScript 3接口,它是我们为Flash Player的新行为在as3中的核心。

public interface IDisposable {
  public function dispose():void;
}

正如您所期望的那样,dispose 方法应该关闭所有资源并丢弃任何可能的引用。

C++ 程序员可能会嘲笑这个“创新”的接口(这是完全可以理解的),但是 as3 在 Flash 中引入了许多与内存管理相关的问题。对于许多编译语言来说,这些问题都是老生常谈,但是 actionscript 程序员现在才第一次遇到这些挑战。

是的,它仍然是一种垃圾收集语言。但不管怎样,与 ActionScript 2 相比,这里有更少的“手把手指导”,正如此接口的需求所证明的那样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接