当对集合的引用未更改时,我是否应该返回集合

本文关键字:集合 是否 返回 引用 | 更新日期: 2023-09-27 17:56:05

我得到了一个接受集合的方法,如下所示

 public IList<CountryDto> ApplyDefaults(IList<CountryDto> dtos)
        {
            //Iterates the collection
                //Validates the items in collection
                //If items are invalid
                //Removes items e.g dtos.Remove(currentCountryDto)
            return dtos;//Do I need to do this?
        }

我的问题是,因为对集合的引用没有更改,我应该从该方法再次返回集合吗?

  1. 对于:通过返回集合,我在签名中明确表示,并且用户知道集合中的项目可能与原始来源不同。有点避免歧义。
  2. 反对:由于验证不会更改集合的引用,因此从技术上讲,返回它没有意义。

在这种情况下,最好的方法是什么?
注意:我不确定这个问题是否基于意见。我想可能我在设计方面错过了一些东西。

当对集合的引用未更改时,我是否应该返回集合

在每种编程语言中,您自己的代码/库与核心库方法的一致性具有很高的价值。因此,检查 Collections.sort() 或 Collection.swap() 和 Collections.shuffle() 是如何定义的,如果您打算修改它,我建议不要返回输入参数。此外,您的方法应以这样一种方式命名,以便输入参数被修改。否则,您的方法将被视为有副作用。

返回一个值通常表明它是一个反映工作的新实例,由方法执行,或者在构建器的情况下用于方法链。

鉴于您的意见/要求:

  1. 如果应用了默认值,则无需报告。
  2. ApplyDefaults很复杂,并且调用其他服务,并不打算生成流畅的API
  3. ApplyDefaults是一个"黑匣子";注入验证逻辑,因此调用代码不知道/关心验证

我认为基于这些,即使没有应用验证,此方法也绝对不应该返回对传入列表的引用。首先,除非 API 显然是围绕方法链构建的(您表示不需要),否则返回 List<T> 类型通常表示正在创建新的列表。其次,如果未创建新列表,用户可能会发现自己以意想不到的方式修改列表。

考虑:

IList<CountryDto> originalCountries = Service.GetCountries();
IList<CountryDto> validatedCountries = ApplyDefaults(originalCountries);
validatedCountries.Add(mySpecialCountry);
OutputOriginalCountries(originalCountries);
OutputValidatedCountries(validatedCountries);

这段代码不是很特别,而且是一种相当常见的模式。如果ApplyDefaults返回对同一originalCountries集合的引用,则mySpecialCountry也会添加到originalCountries。这将违反最小惊讶原则。

如果此行为根据是否验证/筛选项目而改变,则会加剧这种情况。由于验证逻辑是调用方不知道或不关心的行为黑盒,因此 API 使用者不能依赖于它是否返回相同的引用。他们要么必须做自己的参考检查(例如,if (myValidatedCountries == myInputCountries)),要么每次都做一份副本。无论如何,这成为程序员在使用API时必须处理的另一种奇怪的行为。

我认为该方法应该:

A) 始终返回一个复制的列表,其中的项目被过滤掉 ( public IList<CountryDto> ApplyDefaults(IEnumerable<CountryDto> dtos)

B) 就地修改传入列表 ( public void ApplyDefaults(IList<CountryDto> dtos)

对于选项 A,根据列表的大小,即使未执行筛选,每次创建复制列表也可能产生不必要的工作。但是,验证/筛选逻辑可能更简单。也许可以使用 LINQ 查询很好地应用筛选。此外,从列表中删除项通常成本高昂,因为它必须重建内部数组。因此,构建新列表实际上可能更快。您甚至可以将此处的签名简化为IEnumerable<CountryDto>;这允许更广泛的使用,并且非常明显地表明您正在创建新集合。

对于选项 B,如果不需要验证,则不做任何工作,该方法基本上是"自由的"(没有数组重建,没有复制,没有引用更改)。但是,如果有重要的验证,则删除方面可能会很昂贵。由于不是方法链接,因此此版本具有void返回类型,因为开发人员更明显地知道这是就地修改列表。这遵循其他常见的方法,如 List<T>.Sort .此外,如果用户想要一个单独的originalCountriesvalidatedCountries他们总是可以制作副本:

var validatedCountries = originalCountries.ToList();
ApplyDefaults(validatedCountries);

最终,选择哪一个可能取决于性能。如果验证/删除既便宜又罕见,则就地修改列表可能是最好的。如果您预计列表会有很多更改,那么每次生成新副本可能会更快。

无论如何,我建议您也更清楚地命名该方法。例如:

public IList<CountryDto> GetValidCountries(IEnumerable<CountryDto> dtos)
public void RemoveInvalidCountries(IList<CountryDto> dtos)

当然,根据您的实际代码上下文,命名可能会有所不同(我怀疑ApplyDefaults是一个通用/继承的方法名称,而不是特定于CountryDto

我宁愿返回boolean(或enum在一个精心设计的情况下:收藏完好无损地保存已更改无法验证等)

// true if the collection is changed, false otherwise
public Boolean ApplyDefaults(IList<CountryDto> dtos) {
  Boolean result = false;
  //Iterates the collection
  //Validates the items in collection
  //If items are invalid:
  //  Removes items e.g dtos.Remove(currentCountryDto)
  //  result = true;
  ...
  return result; 
}
...
if (ApplyDefaults(myData)) {
  // Collection is changed, do some extra stuff
}

首先:您无法通过参数更改发送的集合的引用,因为默认情况下您将获得它的副本。您需要使用 ref 关键字才能更改它。

其次:如果你的方法有一个返回类型,那么它必须返回一个对象。你的方法不叫GetNewCollectionWithAppliedDefaults,而是ApplyDefaults,这意味着集合将被修改。您应该返回布尔值 true/false 以通知用户更改已完成,或者始终返回参数的集合(以允许嵌套方法调用)。

另外,为什么您认为退回收藏没有意义?我想说没有反对它的论据。把问题转过来:"我为什么不归还集合,它会损害我的代码吗"?

从技术上讲,我会说两者之间没有太大区别。

但是,正如您所指出的,一个常用的约定是函数应该只返回它创建的对象。基本上,这意味着返回对象的函数正在生成一个对象,而不返回任何内容的函数正在修改作为参数传递的对象。

同样,这只是一个约定,在 C# 社区中并未广泛使用,但在 Python 社区中,它是。

有些人会返回布尔值(或错误代码)作为错误的指示符(如旧的 dos 命令行)。我不喜欢这种方法,并且更喜欢提出我以后可以处理的异常。

最后,就我而言,最好的方法是返回一个值,该值指示函数是否完成了更改,并最终返回一个值,指示完成了多少更改。它可以是布尔值,也可以是插入/删除元素的数量......

在任何情况下,尽量与您选择的方法保持一致,如果不是在所有代码中,至少在单个项目中保持一致。有时,您别无选择,只能遵守队友使用的惯例。

(我的回答是基于 Java 的观点;C++和 C# 程序员可能有不同的看法。 我认为最好归还收藏品。 您返回的集合与给定的集合相同,这一事实只是一个实现细节,在代码的未来版本中,您可能希望更改它。 返回的集合可能与传入的文档不同。

另一方面,如果要锁定此方法就地修改集合的设计,请以这种方式记录它,并且不要返回集合。 我不喜欢这样做,但我可以看到在某些情况下的优势。

在你的情况下,我会留下空白,因为ApplyDefaults清楚地说明了它的作用。此外,在集合本身中应用默认值可能是一个好主意。子类 IList 或 List 或其他什么,然后你会这样称呼:

myCollection.ApplyDefaults();

这是显而易见的。