如何在不依赖副作用的情况下创建两个序列? [英] How to create two sequences without relying on side effects?

查看:47
本文介绍了如何在不依赖副作用的情况下创建两个序列?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设您必须处理一个 InputType 序列,该序列产生两个序列,一个是 OutputType 类型,另一个是 ErrorType 类型.

一个基本的实现可以是:

class SeqProcessor {私有 IEnumerable错误类型;公共序列处理器(){this.errorTypes = Enumerable.Empty;}公共 IEnumerable错误{得到 { 返回 this.errors;}}公共 IEnumerableProcessItems(IEnumerable inputTypes){yield return new OutputType();if (err) this.errorTypes = this.errorTypes.Concat(new ErrorType());yield return new OutputType();yield return new OutputType();if (err) this.errorTypes = this.errorTypes.Concat(new ErrorType());//...产量中断;}}

我看到了这两种选择,例如:

  • OutputTypeErrorType 之间使用一个通用接口(例如IProduct)并让ProcessItems返回 IEnumerable(而不是使用 Linq 进行区分).

  • 定义一个名为 NoErrorErrorType 子类,并让 ProcessItems 返回元组 IEnumerable>(如果没有错误,元组中将使用NoError).

由于 ErrorType 在语义上与 OutputType 不同,混合这些类型可能违反单一职责原则.使用委托是一种可接受的替代设计吗:

class SeqProcessor {公共 IEnumerable处理项目(IEnumerable输入类型,操作<错误类型>错误){yield return new OutputType();//...onError(new ErrorType());}}

在这种情况下您使用哪种方法?

解决方案

根据您想要实现的具体目标,我在这里看到了多种可能的解决方案:

  1. 保持原始实现(我将替换 private IEnumerable errorTypes 但允许您确定错误所属的项目).在这种情况下,您遇到的错误具有警告的意义(这就是为什么我更喜欢名称Warning),因为它们与实际结果分开.

  2. 对两种结果类型(即输出和错误)使用通用接口只有在使用结果列表的其他函数可以真正使用错误输出时才有意义.我怀疑这是否是您的意图,但恕我直言,这将是有效的设计选择.

  3. 正如 Pieter 指出的那样,拥有 ErrorType 的子类 NoError 真的很讨厌.然而,更好的解决方案是使用 ResultType 作为 NoErrorError 类型的基础.这样,您就真正拥有了基类的专业化.不过,我想知道输出是否会包含在出现错误的情况下.原始元素?已处理但无效的元素?空值?根据您想要实现的目标,这可能是合理的,但是从给定的信息中很难判断出这一点,老实说,我怀疑这就是您想要的.

    立>
  4. OnError 在很多情况下都是很好的做法,因为它提供了很大的灵活性.但是,在这种情况下,您仍然需要考虑结果中的相应条目是什么.恕我直言,为了避免对 null 或特殊值的处理,最好将其省略掉.

总而言之,OnError 方法似乎是最有前途的,尽管附加信息可能会促使您采用其他提到的方法之一.

Suppose you have to process a sequence of InputType that produces two sequences one of type OutputType and the other of type ErrorType.

A basic implementation could be:

class SeqProcessor {
  private IEnumerable<ErrorType> errorTypes;

  public SeqProcessor()
  {
    this.errorTypes = Enumerable.Empty<ErrorType>;
  }

  public IEnumerable<ErrorType> Errors
  {
    get { return this.errors; } 
  }

  public IEnumerable<OutputType> ProcessItems(IEnumerable<InputType> inputTypes)
  {
     yield return new OutputType();
     if (err) this.errorTypes = this.errorTypes.Concat(new ErrorType());
     yield return new OutputType();
     yield return new OutputType();
     if (err) this.errorTypes = this.errorTypes.Concat(new ErrorType());
     // ...
     yield break;
  }
}

I see these two alternatives for example:

  • Use a common interface (eg. IProduct) between OutputType and ErrorType and let ProcessItems return IEnumerable<IProduct> (than discriminate using Linq).

  • Define a subclass of ErrorType called NoError and let ProcessItems return tuples IEnumerable<Tuple<OutputType, ErrorType>> (if no error, NoError will be used in the tuple).

Edit:

Since ErrorType are semantically different from OutputType, mixing these types could be a violation of Single Responsibility Principle. Can the use of a delegate be an acceptable alternative design:

class SeqProcessor {
  public IEnumerable<OutputType> ProcessItems(
    IEnumerable<InputType> inputTypes,
    Action<ErrorType> onError)
  {
    yield return new OutputType();
    // ...
    onError(new ErrorType());
  }
}

Which approach do you use in such cases?

解决方案

Depending on what exactly you want to achieve, I see multiple possible solutions here:

  1. Stay with the original implementation (where I would replace private IEnumerable<ErrorType> errorTypes but something that allows you to determine the item the error belongs to). In this context, the errors you are encountering would have the significance of a warning (which is why I would also prefer the name Warning) because they are separated from the actual result.

  2. Using a common interface for both result types (that is, output and error) would only make sense if other functions consuming the resulting list could really make use of the error output. I doubt that this is what you intended but imho, this would be valid design choice.

  3. As Pieter pointed out, having a sub-class NoError of ErrorType would really be nasty. However, a nicer solution would be using ResultType as a base for the types NoError and Error. That way, you really have specialization of the base class. Still, I wonder that the output will contain in case of an error. The original element? A processed, but invalid element? Null? Depending on what you want to achieve, this could be reasonable, but this is hard to tell from the given information and, to be honest, I doubt that is what you want.

  4. The OnError is good practice in many contexts because it allows for great flexibility. However, you will still have to think about what will be the corresponding entry in the result in such a case. Imho, it will probably be the best choice to simply leave it out in order to avoid the treatment of either null or either special values.

All in all, it seems like the OnError approach seems to be most promising, even though additional information may drive you towards one of the other mentioned approaches.

这篇关于如何在不依赖副作用的情况下创建两个序列?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆