避免泛型函数中超出范围的问题

本文关键字:范围 问题 中超 泛型 函数 | 更新日期: 2023-09-27 18:03:49

我来自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#中有很多疯狂的事情我不知道,对于我正在创建的这个应用程序,我非常,非常经常必须从列表中选择一个随机元素,该元素可以在其计数中不断递减。有没有更好的办法?

避免泛型函数中超出范围的问题

你的选择是

return default(T)

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

或者你可以像你说的那样返回-1,但这与你的代码是耦合的。

或者您可以返回null,但这只能在T是可空类型的情况下完成,当然。

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

所以最好的选择可能是抛出一个异常:
throw new InvalidOperationException();

使用这种方法,快速失败,并确保没有超出调用者意图的意外发生。

备份此选项的另一个原因。以Linq的扩展方法为例。如果您在空列表上调用First(), Single()Last(),您将获得带有消息"序列不包含元素"InvalidOperationException。给你的类一个类似于框架类的行为总是一件好事。


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


第二个边注。您正在声明您的函数作为IList<T>的扩展方法(您通过在第一个参数之前使用this来做到这一点),但随后您像调用静态助手方法一样调用它。扩展方法是一个语法糖,而不是这样做:

foo = Utilites.PickRandom(myList);

允许您这样做:

foo = myList.PickRandom();

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

另一种替代方法是下面这对重载来代替原来的重载。有了这些,调用者应该清楚地知道,如果不能从列表中"选择"一个值,它们将提供一个默认的随机值。

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作为类的静态成员字段,而不是一次又一次地重新实例化它。查看这篇文章的答案:一个"静态"的正确方法随机的。c#的下一步?

另一个选择是使用Maybe<T>单子。它与Nullable<T>非常相似,但可以使用引用类型。

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方法命名为maybe。

应该是这样的:

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