强制函数输入参数不可变?

Force function input parameters to be immutable?

我刚刚花了 2 天的大部分时间试图追踪一个错误,结果我不小心改变了作为函数输入提供的值。

    IEnumerable<DataLog>
    FilterIIR(
        IEnumerable<DataLog> buffer
    ) {
        double notFilter = 1.0 - FilterStrength;

        var filteredVal = buffer.FirstOrDefault()?.oilTemp ?? 0.0;
        foreach (var item in buffer)
        {
            filteredVal = (item.oilTemp * notFilter) + (filteredVal * FilterStrength);

            /* Mistake here!
            item.oilTemp = filteredValue;
            yield return item;
            */

            // Correct version!
            yield return new DataLog()
            {
                oilTemp = (float)filteredVal,
                ambTemp = item.ambTemp,
                oilCond = item.oilCond,
                logTime = item.logTime
            };
        }
    }

我的首选编程语言通常是 C# 或 C++,具体取决于我认为更适合要求的语言(这是更适合 C# 的更大程序的一部分)...

现在在 C++ 中,我可以通过接受常量迭代器来防止这样的错误,这会阻止您在检索值时修改它们(尽管我可能需要为 return 值)。我做了一些搜索,但找不到在 C# 中执行此操作的任何简单方法,有人知道不同的方法吗?

我想我可以制作一个 IReadOnlyEnumerable<T> class 将 IEnumerable 作为构造函数,但后来我意识到除非它在您检索时复制值它们实际上不会有任何影响,因为基础值仍然可以修改。

有什么方法可以防止将来出现此类错误?一些包装器 class,或者即使它是我想要保护的每个函数顶部的一小段代码,任何东西都可以。

目前我能想到的唯一可行的合理方法是为我需要的每个 class 定义一个 ReadOnly 版本,然后有一个非只读版本继承并重载属性并添加函数以提供相同 class.

的可变版本

问题不在于 IEnumerableIEnumerable 实际上是 不可变的 。您不能在其中添加或删除内容。可变的是你的 DataLog class.

因为DataLog是引用类型,所以item持有对原始对象的引用,而不是对象的副本。这一点,加上 DataLog 是可变的,允许您改变传入的参数。

所以在高层次上,您可以:

  • 复制 DataLog,或者;
  • 使DataLog不可变

或两者...

你现在做的是"making a copy of DataLog"。另一种方法是将 DataLogclass 更改为 struct。这样,在将它传递给方法时,您将始终创建它的副本(除非您用 ref 标记参数)。所以在使用此方法时要小心,因为它可能会悄悄地破坏假定传递引用语义的现有方法。

您还可以使 DataLog 不可变。这意味着删除所有的设置器。可选地,您可以添加名为 WithXXX 的方法,即 returns 与对象的副本只有一个 属性 不同。如果您选择这样做,您的 FilterIIR 将如下所示:

yield return item.WithOilTemp(filteredVal);

The only sort of reasonable approach I can think of at the moment that'll work is to define a ReadOnly version of every class I need, then have a non-readonly version that inherits and overloads the properties and adds functions to provide a mutable version of the same class.

您实际上不需要这样做。请注意 List<T> implements IReadOnlyList<T>,尽管 List<T> 显然是可变的。您可以编写一个名为 IReadOnlyDataLog 接口 。该接口只有 DataLoggetters。然后,让 FilterIIR 接受 IEnumerable<IReadOnlyDataLog>DataLog 实施 IReadOnlyDataLog。这样,您就不会意外地改变 FilterIIR.

中的 DataLog 对象