参数可以不变吗?

问题描述 投票:76回答:9

我正在寻找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.
}
c# const readonly parameter-passing
9个回答
58
投票

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

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

readonly关键字只能用于字段。

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

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


16
投票

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

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


8
投票

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

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

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


8
投票

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

我同意Bennett Dill的观点。

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


7
投票

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

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

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

祝好运 :-)


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


3
投票

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

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


2
投票

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

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;
  }

}

0
投票

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.