强制函数输入参数不可变?
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.
的可变版本
问题不在于 IEnumerable
。 IEnumerable
实际上是 不可变的 。您不能在其中添加或删除内容。可变的是你的 DataLog
class.
因为DataLog
是引用类型,所以item
持有对原始对象的引用,而不是对象的副本。这一点,加上 DataLog
是可变的,允许您改变传入的参数。
所以在高层次上,您可以:
- 复制
DataLog
,或者;
- 使
DataLog
不可变
或两者...
你现在做的是"making a copy of DataLog
"。另一种方法是将 DataLog
从 class
更改为 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
的 接口 。该接口只有 DataLog
的 getters。然后,让 FilterIIR
接受 IEnumerable<IReadOnlyDataLog>
并 DataLog
实施 IReadOnlyDataLog
。这样,您就不会意外地改变 FilterIIR
.
中的 DataLog
对象
我刚刚花了 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.
问题不在于 IEnumerable
。 IEnumerable
实际上是 不可变的 。您不能在其中添加或删除内容。可变的是你的 DataLog
class.
因为DataLog
是引用类型,所以item
持有对原始对象的引用,而不是对象的副本。这一点,加上 DataLog
是可变的,允许您改变传入的参数。
所以在高层次上,您可以:
- 复制
DataLog
,或者; - 使
DataLog
不可变
或两者...
你现在做的是"making a copy of DataLog
"。另一种方法是将 DataLog
从 class
更改为 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
的 接口 。该接口只有 DataLog
的 getters。然后,让 FilterIIR
接受 IEnumerable<IReadOnlyDataLog>
并 DataLog
实施 IReadOnlyDataLog
。这样,您就不会意外地改变 FilterIIR
.
DataLog
对象