【问题标题】:Avoiding out of range issues in generic functions避免泛型函数中的超出范围问题
【发布时间】:2015-07-22 00:15:58
【问题描述】:

我来自 PHP 和 Javascript 的狂野西部,您可以从函数返回任何内容。虽然我确实讨厌缺乏责任感,但我在努力保持我的代码“完美”方面也面临着新的挑战。

我做了这个通用函数来从列表中选择一个随机元素

public static T PickRandom<T>(this IList<T> list) {
    Random random = new Random();
    int rnd = random.Next(list.Count);
    return list[rnd];
}

但我想保护自己不要在值为 0 的列表中使用它。显然我不能从这个函数返回除 T 以外的任何东西,例如 false 或 -1。我当然可以这样做

if(myList.Count > 0)
   foo = Utilites.PickRandom(myList);

但是,C# 中有很多我不知道的疯狂事物,对于我正在创建的这个应用程序,我经常不得不从列表中选择一个随机元素,该元素的计数可能会不断递减。有没有更好的办法?

【问题讨论】:

  • 如果有人在空列表上调用此方法会发生什么?
  • 如果列表包含引用类型(例如对象),您可以这样做。但这不适用于int 之类的东西,除非你明确说它可以为空。在我看来,在一个空列表上调用这个方法是 invalid 的,所以例外应该没问题。返回 null 对于调用者来说可能是意外的。如果 PickRandom 方法中的 count == 0,我会抛出异常。此外,您应该使用这个:random.Next(list.Count - 1),否则您会在尝试访问 last+1 元素时遇到异常。
  • @Rob 我对你的最后一句话感到困惑。假设您有一个包含 4 个元素的列表。如果你调用 random.Next(list.Count) 它应该只返回 0/1/2/3 no?
  • 你是对的,@user3822370。 Next(int) 返回介于 0value - 1 之间的任何值。
  • 对不起,你是对的。我将它与另一个具有最大值 inclusive 的库混淆了。

标签: c# generics


【解决方案1】:

你的选择是

return default(T)

这将是一种模棱两可的行为,因为它可能是列表的有效元素。

或者您可以像您所说的那样返回 -1 之类的东西,但这与您的代码非常相关。

或者你也可以返回null,当然,只有当T 是可空类型时才能这样做。

在所有以前的情况下,如果调用者不知道这种情况,应用程序可能会继续使用无效值,从而导致未知后果

所以最好的选择可能是抛出异常:

throw new InvalidOperationException();

使用这种方法,您可以快速失败,并确保不会发生超出调用者意图的意外情况。

支持此选项的更多理由。以 Linq 的扩展方法为例。如果你在一个空列表上调用First()Single()Last(),你会得到一个InvalidOperationException 和消息“序列不包含元素”。为您的类提供类似于框架类的行为总是一件好事。


感谢 Alexei Levenkov 在问题中的评论,我正在添加一个旁注。随机生成不是最好的方法。看看this question


第二个附注。您将您的函数声明为IList&lt;T&gt; 的扩展方法(通过在第一个参数之前使用this 来做到这一点),但随后您将其称为静态辅助方法。扩展方法是一种语法糖,而不是这样做:

foo = Utilites.PickRandom(myList);

让你这样做:

foo = myList.PickRandom();

更多关于扩展方法的信息可以在here找到。

【讨论】:

  • 我会说这是正确的答案。但是,我希望您能强调返回某些显着值的问题。即,一些调用者几乎肯定会忘记检查值的事实。因此,允许索引器抛出IndexOutOfRangeException 可能是最好的举措(您已经说过)。
  • 感谢您的回复。现在我只是无法看到调用者如何处理这个问题。 PickRandom 抛出异常,我会以某种方式处理它,但是调用者会做什么呢?它怎么知道保释?它需要自己的 try/catch 吗?
  • 是的,调用者必须知道这种情况,你(作为扩展方法)不负责决定在这种情况下做什么。因此,他应该在调用之前检查列表计数或进行尝试捕获。鉴于这种情况,第一种方法更好,因为空列表是您可能期望的,而不是不可预见的(这就是您应该使用 try-catch 的地方)。
【解决方案2】:

另一种选择是下面的一对重载而不是原来的。有了这些,调用者应该清楚他们将提供一个默认随机值,以防无法从列表中“挑选”一个。

public static T PickRandomOrReturnDefault<T>(this IList<T> list, T defaultRandomValue)
{
    if (list == null || list.Count == 0) return defaultRandomValue;

    Random random = new Random();
    int rnd = random.Next(list.Count);
    return list[rnd];
}

public static T PickRandomOrReturnDefault<T>(this IList<T> list, Func<T> createRandomValue)
{
    if (list == null || list.Count == 0) return createRandomValue();

    Random random = new Random();
    int rnd = random.Next(list.Count);
    return list[rnd];
}

注意:您可能应该考虑将 random 作为类的静态成员字段,而不是一遍又一遍地重新实例化它。看到这个帖子的答案Correct method of a "static" Random.Next in C#?

【讨论】:

    【解决方案3】:

    您的另一个选择是使用Maybe&lt;T&gt; monad。它非常类似于 Nullable&lt;T&gt;,但适用于引用类型。

    public class Maybe<T>
    {
        public readonly static Maybe<T> Nothing = new Maybe<T>();
    
        public T Value { get; private set; }
        public bool HasValue { get; private set; }
    
        public Maybe()
        {
            HasValue = false;
        }
    
        public Maybe(T value)
        {
            Value = value;
            HasValue = true;
        }
    
        public static implicit operator Maybe<T>(T v)
        {
            return v.ToMaybe();
        }
    }
    

    您的代码可能如下所示:

    private static Random random = new Random();
    public static Maybe<T> PickRandom<T>(this IList<T> list)
    {
        var result = Maybe<T>.Nothing;
        if (list.Any())
        {
            result = list[random.Next(list.Count)].ToMaybe();
        }
        return result;
    }
    

    你会像这样使用它:

    var item = list.PickRandom();
    
    if (item.HasValue) { ... }
    

    就我个人而言,我将可能的方法命名为最后的 Maybe。

    应该是这样的:

    var itemMaybe = list.PickRandomMaybe();
    
    if (itemMaybe.HasValue) { ... }
    

    【讨论】:

      猜你喜欢
      • 2013-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-22
      • 2016-07-22
      • 2018-06-18
      • 2023-03-09
      • 2021-11-20
      相关资源
      最近更新 更多