参数可以不变吗?

时间:2010-02-26 02:13:32

标签: c# const readonly parameter-passing

我正在寻找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.
}

9 个答案:

答案 0 :(得分:58)

不幸的是你无法在C#中做到这一点。

const关键字只能用于本地变量和字段。

readonly关键字只能用于字段。

  

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

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

答案 1 :(得分:17)

现在可以在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标记参数意味着:

  

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

答案 2 :(得分:8)

我将从int部分开始。 int是一种值类型,在.Net中,这意味着您确实正在处理副本。这是一个非常奇怪的设计约束,告诉方法“你可以得到这个值的副本。这是你的副本,而不是我的副本;我永远不会再看到它了。但你无法改变副本。”在方法调用中隐含了复制此值是可以的,否则我们无法安全地调用该方法。如果该方法需要原始文件,请将其留给实施者进行复制以保存原文。要么给方法赋值,要么不给方法赋值。不要在两者之间全力以赴。

让我们继续讨论类型。现在它有点令人困惑。你的意思是一个恒定的引用,其中引用本身不能被更改,或者一个完全锁定,不可更改的对象?如果是前者,默认情况下.Net中的引用是按值传递的。也就是说,您获得了参考文献的副本。因此,我们与价值类型的情况基本相同。如果实现者需要原始引用,他们可以自己保留它。

这只会让我们看到常量(锁定/不可变)对象。从运行时的角度来看,这似乎没什么问题,但编译器如何强制执行呢?由于属性和方法都有副作用,因此基本上只限于只读字段访问。这样的对象不太可能非常有趣。

答案 3 :(得分:8)

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

我同意Bennett Dill。

const关键字非常有用。在这个例子中,你使用了一个int,人们不明白你的观点。但是,为什么如果你的参数是一个用户庞大而复杂的对象,在该函数内部无法更改?这是使用const关键字:参数不能在该方法内部进行更改,因为[此处无论什么原因]对该方法无关紧要。 Const关键字非常强大,我真的很想念C#。

答案 4 :(得分:7)

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

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

我意识到这可能是一个非常明显的工作,已经考虑过了,并且OP试图通过提出这个问题来避免这样做,但我觉得它应该在这里,而且不会少......

祝你好运: - )

答案 5 :(得分:4)

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

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和其他此类规则。

答案 6 :(得分:3)

如果你经常遇到这样的麻烦,那么你应该考虑“应用匈牙利”。好的,与bad kind相反。虽然这通常不会尝试表达方法参数的常量(这太不寻常),但肯定没有什么可以阻止您在标识符名称之前添加额外的“c”。

对于所有那些渴望抨击downvote按钮的人,请阅读这些主题的意见:

答案 7 :(得分:2)

我知道这可能有点晚了。 但对于那些仍在寻找其他方式的人来说,可能还有另一种方法可以解决这种C#标准的限制问题。 我们可以编写包装类ReadOnly&lt; T&gt;其中T:struct。 用隐式转换为基类型T. 但只有显式转换为包装器&lt; T&gt;类。 如果开发人员尝试将隐式设置为ReadOnly&lt; T&gt;的值,则会强制执行编译器错误。类型。 我将在下面展示两种可能的用途。

USAGE 1需要更改调用者定义。此用法仅用于测试“TestCalled”功能代码的正确性。在发布级别/版本中,您不应该使用它。由于大规模的数学运算可能会导致转换过度,并使代码变慢。我不会使用它,但出于演示目的,我只发布了它。

我建议的USAGE 2,在TestCalled2函数中演示了Debug vs 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;
  }

}

答案 8 :(得分:0)

如果将struct传递给方法,除非它由ref传递,否则它不会被传入的方法更改。所以在这个意义上,是的。

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

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

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

相关问题