参数可以保持不变吗?

Posted

技术标签:

【中文标题】参数可以保持不变吗?【英文标题】:Can parameters be constant? 【发布时间】:2011-01-21 07:08:05 【问题描述】:

我正在寻找与 Java 的 final 等效的 C#。它存在吗?

C# 是否有类似以下的内容:

public Foo(final int bar);

在上面的例子中,bar 是一个只读变量,不能被Foo() 更改。有没有办法在 C# 中做到这一点?

例如,也许我有一个长方法可以处理某些对象(整数)的xyz 坐标。我想绝对确定该函数不会以任何方式更改这些值,从而破坏数据。因此,我想将它们声明为只读。

public Foo(int x, int y, int z) 
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.

【问题讨论】:

***.com/questions/2125591/… 的副本(顺便说一句,Eric Lippert 给出了很好的答案) 我不认为它完全重复。这个问题是关于按引用传递和按值传递之间的区别。我认为 Rosarch 可能使用了错误的示例代码来表达他试图表达的观点。 @Rosarch:我对“最终”一词的理解是,无论对象是什么,您都不能再对对象执行任何操作。我知道应用于类的“最终”将等同于 C# 中的“密封”。但是无论如何,这个关键字“final”的好处或实际用法是什么?有一天,我几乎在任何地方都可以在 JAVA 源代码中看到它。 @Will Marcouiller 使用 final 关键字它不是不能改变的对象,因为你可以使用改变对象内部的方法,它是对不能改变的对象的引用。在按值传递的情况下,就像给出的示例一样,它会阻止 x++ 成为有效操作,因为值会发生变化。这样做的好处是您可以进行编译时完整性检查,以确保没有将值设置为不同的值。但是,根据我的经验,我从来不需要这样的功能。 +1 @Corey Sunwold:感谢您的精确度。至于现在,我只知道C#和JAVA之间的相似之处,知道C#在概念上“继承”了JAVA。这就是说,它鼓励我更多地了解 JAVA,因为我知道它在全球范围内广泛传播。 【参考方案1】:

很遗憾,您不能在 C# 中执行此操作。

const 关键字只能用于局部变量和字段。

readonly 关键字只能用于字段。

注意:Java 语言还支持为方法设置最终参数。 C# 中不存在此功能。

来自http://www.25hoursaday.com/CsharpVsJava.html

编辑(2019/08/13): 我将其放入以提高知名度,因为这是公认的并且在列表中最高。现在可以使用in 参数。详情见下方the answer。

【讨论】:

“很不幸”?这与当前的能力有何不同? @John Saunders 很不幸,因为 Rosarch 正在寻找一个不存在的功能。 @John:在方法中它不是恒定的。这就是问题所在。 它唯一的常量超出了这个方法的范围。忽略它是按引用传递还是按值传递,Rosarch 不希望参数在方法范围内发生变化。这是关键的区别。 @silky:阅读他的帖子,这不是他想要的。他要求确保该方法不会改变实际参数。【参考方案2】:

现在可以在 C# 7.2 版中实现:

您可以在方法签名中使用in 关键字。 MSDN documentation.

in 关键字应在指定方法的参数之前添加。

示例,C# 7.2 中的有效方法:

public long Add(in long x, in long y)

    return x + y;

虽然以下是不允许的:

public long Add(in long x, in long y)

    x = 10; // It is not allowed to modify an in-argument.
    return x + y;

尝试修改xy 时将显示以下错误,因为它们标有in

不能分配给变量'in long',因为它是一个只读变量

in 标记参数意味着:

此方法不会修改用作此参数的参数的值。

【讨论】:

当然,它对引用类型没有用处。对具有这些类型的参数使用 in 关键字,仅防止分配给它们。不阻止修改其可访问的字段或属性!我说的对吗? 请注意,它只适用于结构/值而不适用于类,您可以在其中修改 ref 的实例,这样会更糟。而且您不会收到任何警告。谨慎使用。 blog.dunnhq.com/index.php/2017/12/15/…【参考方案3】:

答案:C# 没有 C++ 那样的 const 功能。

我同意 Bennett Dill 的观点。

const 关键字非常有用。在示例中,您使用了 int 并且人们不明白您的意思。但是,为什么如果你的参数是一个用户巨大而复杂的对象,不能在该函数内更改呢?这就是 const 关键字的使用:参数不能在该方法内更改,因为 [这里的任何原因] 这对该方法无关紧要。 const 关键字非常强大,在 C# 中我真的很怀念它。

【讨论】:

【参考方案4】:

这是一个简短而甜蜜的答案,可能会得到很多反对票。我没有阅读所有的帖子和cmets,所以如果以前有人建议过,请原谅我。

为什么不获取参数并将它们传递给一个对象,将它们公开为不可变,然后在你的方法中使用该对象?

我意识到这可能是一个已经考虑过的非常明显的解决方法,并且 OP 试图通过提出这个问题来避免这样做,但我觉得它应该在这里无论如何......

祝你好运:-)

【讨论】:

因为成员仍然可以被修改。此 c++ 代码显示:int* p; *p = 0;。这将编译并运行,直到分段错误。 我投了反对票,因为它不能解决问题。您还可以将参数保存在函数头部并在最后进行比较,并在更改时引发异常。如果有最坏的解决方案竞赛,我会把它放在我的后兜里:)【参考方案5】:

我将从int 部分开始。 int 是一种值类型,在 .Net 中这意味着您确实在处理副本。告诉一个方法“你可以拥有这个值的副本。这是你的副本,不是我的;我再也见不到它了。但你不能更改副本。”这是一个非常奇怪的设计约束。在方法调用中暗示复制这个值是可以的,否则我们不能安全地调用该方法。如果该方法需要原件,请将其留给实施者制作副本以保存。要么给方法赋值,要么不给方法赋值。不要在两者之间胡思乱想。

让我们继续讨论引用类型。现在它变得有点混乱。您是指一个常量引用,其中引用本身不能更改,还是一个完全锁定、不可更改的对象?如果是前者,.Net 中的引用默认按值传递。也就是说,您会获得一份参考资料。所以我们的情况与值类型基本相同。如果实施者需要原始参考,他们可以自己保留。

这只是给我们留下了常量(锁定/不可变)对象。从运行时的角度来看,这似乎没问题,但是编译器如何执行它呢?由于属性和方法都可能有副作用,因此您基本上仅限于只读字段访问。这样的对象不太可能很有趣。

【讨论】:

我因为误解了这个问题而对你投了反对票;这不是在调用之后更改值,而是在其中更改它。 @silky - 我没有误解这个问题。我也在谈论函数内部。我是说发送到函数是一个奇怪的限制,因为它并没有真正停止任何事情。如果有人忘记更改的原始参数,他们很可能会忘记更改的副本。 我不同意。仅提供评论来解释否决票。 DateTime 仅具有只读字段访问权限,但它仍然很有趣。它有许多返回新实例的方法。许多函数式语言避免使用具有副作用的方法,而更喜欢不可变类型,在我看来,这使得代码更易于遵循。 DateTime 也是值类型,不是引用类型。【参考方案6】:

为您的类创建一个只有只读属性访问器的接口。然后让您的参数属于该接口而不是类本身。示例:

public interface IExample

    int ReadonlyValue  get; 


public class Example : IExample

    public int Value  get; set; 
    public int ReadonlyValue  get  return this.Value;  



public void Foo(IExample example)

    // Now only has access to the get accessors for the properties

对于结构,创建一个通用的 const 包装器。

public struct Const<T>

    public T Value  get; private set; 

    public Const(T value)
    
        this.Value = value;
    


public Foo(Const<float> X, Const<float> Y, Const<float> Z)

// Can only read these values

但值得注意的是,你想做你要求做的关于结构的事情,这很奇怪,作为方法的作者,你应该期望知道该方法中发生了什么。在方法中修改它们不会影响传入的值,因此您唯一关心的是确保您在编写的方法中表现自己。有一点需要警惕和干净的代码是关键,而不是强制执行 const 和其他此类规则。

【讨论】:

这其实很聪明。另外我想指出你可以同时继承多个接口——但你只能继承一个类。 这很有帮助......并且以同样的方式减轻了它在 c# 中丢失的烦恼。谢谢 这种类型的编码永远不应该出现在发布版本中,因为如果在数据流上运行,您将添加两个不必要的结构和返回的转换,并产生性能问题。【参考方案7】:

我知道这可能有点晚了。 但是对于仍在寻找其他方法的人来说,可能有另一种方法可以绕过 C# 标准的这个限制。 我们可以编写包装类 ReadOnly where T : struct。 隐式转换为基本类型 T。 但只有显式转换为 wrapper 类。 如果开发人员尝试将隐式设置为 ReadOnly 类型的值,这将强制编译器错误。 我将在下面演示两种可能的用途。

USAGE 1 需要更改调用方定义。此用法仅用于测试“TestCalled”函数代码的正确性。在发布级别/构建时,您不应该使用它。因为在大规模的数学运算中,转换可能会过度杀伤力,并使您的代码变慢。我不会使用它,但我只是出于演示目的发布它。

我建议的USAGE 2 在TestCalled2 函数中演示了Debug 与Release 的使用。使用这种方法时,TestCaller 函数中也不会进行转换,但它需要使用编译器条件对 TestCaller2 定义进行更多编码。您可以在调试配置中注意到编译器错误,而在发布配置中,TestCalled2 函数中的所有代码都会成功编译。

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct

  private VT value;
  public ReadOnly(VT value)
  
    this.value = value;
  
  public static implicit operator VT(ReadOnly<VT> rvalue)
  
    return rvalue.value;
  
  public static explicit operator ReadOnly<VT>(VT rvalue)
  
    return new ReadOnly<VT>(rvalue);
  


public static class TestFunctionArguments

  static void TestCall()
  
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  


【讨论】:

如果 ReadOnly 是 struct 并且 VT 可以是 struct 和 class 会更好,这样如果 VT 是 struct 则值将通过值传递,如果它是类则值将通过引用传递如果你想让它像 java 的 final 一样,运算符 ReadOnly 应该是隐式的。 完全忘记了这一点。是的,你是正确的结构,允许 VT 是任何会是更好的解决方案。现在我记得 left explicit 是为了不把它留在代码中,即:当你有长的带有大小写字符的数学表达式时,你可能最终会混合它们,这将有助于发现混合,但它会降低性能。盒装解决方案 2 对此显式转换是透明的。但不建议任何人使用这种编码风格,今天我们也有 C# 7.2 和 'in' 关键字(如 Max 介绍的)应该满足这一点。【参考方案8】:

如果您经常遇到这样的麻烦,那么您应该考虑使用“应用程序匈牙利语”。好的那种,而不是bad kind。虽然这通常不会试图表达方法参数的恒定性(这太不寻常了),但肯定没有什么可以阻止您在标识符名称前添加一个额外的“c”。

对于所有现在渴望按下否决按钮的人,请阅读这些名人关于该主题的意见:

Eric Lippert Larry Osterman Joel Spolsky

【讨论】:

请注意,您不需要命名约定来强制执行此操作;您总是可以在事后使用分析工具来做到这一点(不是很好,但尽管如此)。我相信宪兵 (mono-project.com/Gendarme) 对此有规定,可能还有 StyleCop/FxCop。 解决方案中最糟糕的尝试(失败)。所以现在你的参数不仅是可写的,它还通过向你谎称它没有改变而让你失败。 到目前为止,两个疼痛的 SO 用户可能会更糟。【参考方案9】:

如果struct被传入一个方法,除非它被ref传递,否则它不会被它传入的方法改变。所以从这个意义上说,是的。

您能否创建一个参数,其值不能在方法内赋值或在方法内不能设置其属性?不可以。您不能阻止在方法中分配值,但可以通过创建不可变类型来阻止设置它的属性。

问题不在于参数或其属性是否可以在方法中分配。问题是当方法退出时它会是什么。

任何外部数据将被更改的唯一情况是您传入一个类并更改它的一个属性,或者如果您使用 ref 关键字传递一个值。你所概述的情况两者都没有。

【讨论】:

我会 +1 除了关于不可变性的评论......拥有不可变类型不会完成他正在寻找的东西。 当然可以,默认情况下。不通过引用传递的不可变类型将确保方法外的值不会改变。 是的,但他的示例是关于更改方法 inside 的值。在他的示例代码中,x 是一个 Int32,它是不可变的,但他仍然可以编写 x++。也就是说,他试图阻止重新分配参数,这与参数值的可变性正交。 @silky 对误解同一个问题的三个答案投反对票有点奇怪。也许这个问题被误解不是读者的错。你知道,当一个男人和他的第三任妻子离婚时,这通常不是妻子的错。 @David:这是投票系统的目的。按相关性排序。我不明白你为什么要纠正它。

以上是关于参数可以保持不变吗?的主要内容,如果未能解决你的问题,请参考以下文章

在java方法调用中保持参数不变

cesium线段大小保持不变

鼠标移到链接上后,HTML 超链接的颜色保持不变

为啥参数的顺序保持不变,即使参数作为命名参数传递?在 JavaScript 中

超参数调优后精度保持不变

在 R 中编程灵敏度分析:改变 1 个参数(列),保持其他参数不变。更好的方法?