获取单个项目或列表的 C# 方法

VSO*_*VSO 2 c#

我有一个方法,它现在需要一个 id。我需要添加一个接受 id 列表的方法。我的选择是让其中一种方法调用另一种方法,如下所示:

void NotifyUsers(List<int> userIds) {//do things}

void NotifyUser(int userId)
{
  NotifyUsers(new List<int>{userId})
}
Run Code Online (Sandbox Code Playgroud)

或者我可以将调用更改为NotifyUsers(new List<int>{9999}.

或者我可以使用泛型等。

这里的“最佳实践”是什么?

我知道这有点基于意见的问题,但似乎应该有一个标准,我找不到它。

Eri*_*ert 19

这里的“最佳实践”是什么?

最佳实践是采取整体方法,特别强调调用者的需求。这并不一定意味着为调用者提供最大的灵活性!这意味着了解调用者的用例。

我个人构建此解决方案的方式是:

// Notice: User, not Users. This notifies a single user.
void NotifyUser(int id) 
{
  // notify the user
}

void NotifyUsers(IEnumerable<int> ids)
{
  foreach(var id in ids) 
    NotifyUser(id);
}
Run Code Online (Sandbox Code Playgroud)

这向调用者强调,如果您想执行一次,请使用NotifyUser,如果您有任何用户序列——不仅仅是一个列表——那么您可以NotifyUsers使用该序列进行调用。

现在我要问的下一个问题是:呼叫者是否想这样做:

NotifyUsers(10, 20, 30);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我会添加第三个函数:

void NotifyUsers(params int[] ids)
{
  NotifyUsers((IEnumerable<int>) ids);
}
Run Code Online (Sandbox Code Playgroud)

这种技术对于调用者来说是灵活的,同时确保您的大多数方法都是简单的单行代码。如果 NotifyUser 中有错误,您只想在一个地方修复它。

这种方法的一个缺点是它NotifyUsers()变得合法并且是无操作的。有人可能会不小心调用它并认为它正在做某事。在这种情况下,您可能会强制至少有一个:

void NotifyUsers(int id, params int[] ids)
{
  NotifyUser(id);
  NotifyUsers((IEnumerable<int>) ids);
}
Run Code Online (Sandbox Code Playgroud)

这说明了一个重要的观点:考虑来电者的需求还涉及对您不认识的人进行心理分析,以弄清楚他们将做错什么,然后在它发生之前加以预防。设计的API,会让人自然而然地成功是很难!

(另外,请注意这些草图省略了错误处理;您可能想要检查序列和数组是否不为空,等等。)

这里的关键是:从了解调用者的需求开始;设计满足他们需求的 API。然后实施它。也就是说,实施选择应该是什么?

这取决于通知一系列用户的工作方式。我上面勾画的解决方案做了一些假设。他们是:

  • 这是不是更有效地通知几百个用户“毕其功于一役”比它是在一个时间一个通知几百个用户。
  • 如果序列中的一个用户的通知失败,正确的做法是停止通知。

如果这些假设是错误的呢?考虑一个更新数据库的 API,代价高昂的部分不是更新而是建立与数据库的连接。在这种情况下,您不想写:

void NotifyUser(int id) 
{
  Connect(); // Expensive
  Update(id);
  Disconnect();
}
void NotifyUsers(IEnumerable<int> ids)
{
  foreach(var id in ids)
    NotifyUser(id);
}
Run Code Online (Sandbox Code Playgroud)

因为现在您要为 100 个用户建立 100 个连接。相反,您想翻转脚本:

void NotifyUser(int id) 
{
  NotifyUsers(Enumerable.Repeat(id, 1));
}
void NotifyUsers(IEnumerable<int> ids)
{
  Connect();
  foreach(var id in ids)
    Update(id);
  Disconnect();
}
Run Code Online (Sandbox Code Playgroud)

请注意,在重写实现时,我没有重写 API;请记住,我们已经将 API 设计为对调用者有益,因此如果此 API 满足他们的需求,请不要更改它!方法是抽象的;我们可以更改细节以满足性能要求。(如果我们因为 API 设计问题而无法满足性能目标,那么我们一开始就没有设计出满足调用者需求的 API。)

现在错误处理呢?有很多可能性:

  • 通知需要尽力而为。如果序列中的一个通知失败,则捕获异常,吃掉它,然后继续与其他用户一起工作。永远不要通知调用者失败
  • 通知需要尽力而为,但要求调用者了解所有失败。记录异常并重新抛出它们或返回失败报告而不是无效。
  • 通知必须是全有或全无。也就是说,如果第十次通知失败,则撤消之前的通知以保持世界一致。这很难。你不能不按铃。

等等。再次非常仔细地考虑调用者的需求。他们期望失败后会发生什么?你知道如何编写逻辑来做调用者期望的事情吗?您能否清楚地记录下来,以便来电者知道是否满足了他们的期望?

  • 为了完整性而接受这一点。感谢大家的帮助。 (2认同)
  • @LucaCremonesi:在方法 `void NotifyUsers(params int[] ids) { NotifyUsers((IEnumerable&lt;int&gt;) ids); 的版本中 }` 强制转换是必要的,因为没有它,你最终会陷入无限递归!params 方法可以以其“未扩展”形式以及“扩展”形式合法地被调用。 (2认同)

Dav*_*vid 5

如果阵列是代替具体地是可接受的List<>话,可以用params关键字。例如:

void NotifyUsers(params int[] userIds)
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

您可以使用零个或多个参数调用它:

NotifyUsers(123);
NotifyUsers(5, 67, 890);
// etc.
Run Code Online (Sandbox Code Playgroud)

  • 听起来这里的第二课也是使用最简单的类型来仅支持方法签名中真正必要的功能。(例如,当只需要枚举集合的能力时,使用“IEnumerable&lt;&gt;”而不是“List&lt;&gt;”) (3认同)