参数可以恒定吗?


83

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

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

stackoverflow.com/questions/2125591/…的副本(并且对Eric Lippert,btw有一个很好的回答)
casperOne 2010年

12
我不认为它的确切副本。这个问题是关于按引用传递和按值传递之间的区别。我认为Rosarch可能已经在他试图克服的问题上使用了错误的示例代码。
科里·桑沃尔德

@Rosarch:据我从“最终”一词了解到的是,无论对象是什么,您都无法再对其进行操作。我知道,应用于类的“最终”将等同于C#中的“密封”。但是,无论如何,此关键字“ final”的好处或实际用途是什么?我有一天几乎在Java源代码中的任何地方都看到了它。
Will Marcouiller

3
@Will Marcouiller使用final关键字不是不能更改的对象,因为您可以使用更改对象内部的方法,它是对不能更改的对象的引用。在按值传递的情况下(如给出的示例),它将防止x ++成为有效操作,因为值将发生变化。这样做的好处是您可以进行编译时的完整性检查,以确保没有任何内容将值设置为不同。但是,以我的经验,我从不需要这样的功能。
科里·桑沃尔德

+1 @Corey Sunwold:非常感谢您。到目前为止,我只知道C#和JAVA之间的相似之处,因为知道C#是从Java继承而来的。这就是说,它鼓励我学习有关JAVA的更多信息,因为我知道它在全球范围内广为传播。
Will Marcouiller

Answers:


60

不幸的是,您无法在C#中执行此操作。

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

readonly关键字只能领域。

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

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

编辑(2019/08/13):我将其扔进去以提高知名度,因为它被接受并且在列表中排名最高。现在可以使用in参数了。有关详细信息,请参见此答案下面的答案


1
“不幸”?这与当前功能有何不同?
约翰·桑德斯

31
@John Saunders不幸的是,Rosarch正在寻找不存在的功能。
科里·桑沃尔德

7
@John:在方法中不是恒定的。这就是问题。
中午丝绸

6
它在此方法范围之外的唯一常数。忽略是通过引用还是通过值传递,Rosarch都不希望参数在方法范围内更改。多数民众赞成在关键区别。
科里·桑沃尔德

5
@silky:读他的帖子,那不是他要的。他要求确保该方法不会更改实际参数。
约翰·桑德斯

30

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

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

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方式标记参数:

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


3
当然,它对于引用类型没有用。in对这些类型的参数使用关键字,只能阻止分配给它们。不要阻止修改其可访问字段或属性!我对吗?
ABS

5
请注意,它仅适用于struct / values而不适用于类,在类中您可以通过ref修改实例,因此情况更糟。而且您不会收到任何警告。请谨慎使用。blog.dunnhq.com/index.php/2017/12/15/...
jeromej

8

这是一个简短而甜蜜的答案,可能会得到很多反对。我尚未阅读所有帖子和评论,因此,如果以前曾建议这样做,请原谅我。

为什么不采用您的参数并将其传递到一个将其暴露为不可变的对象,然后在您的方法中使用该对象呢?

我意识到这可能是已经考虑过的非常明显的解决方案,OP试图通过问这个问题来避免这样做,但是我仍然认为应该在这里...

祝好运 :-)


1
因为成员仍然可以修改。这个C ++代码表明:int* p; *p = 0;。它将编译并运行直到分段错误。
科尔·约翰逊

我不赞成,因为这不是解决问题的办法。您还可以将参数保存在函数头,并在最后进行比较,然后在更改时引发异常。如果有最糟糕的解决方案竞赛,我将把它放在我的后兜里:)
Rick O'Shea '18

8

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

我同意贝内特·迪尔的观点。

const关键字非常有用。在该示例中,您使用了一个int,但是人们并没有理解您的意思。但是,为什么要声明参数是一个用户巨大且复杂的对象,而该对象无法在该函数内更改?那就是使用const关键字:该方法内部的参数无法更改,因为[无论出于何种原因]对该方法都无关紧要。const关键字非常强大,我真的很想念C#。


7

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

让我们继续参考类型。现在有点混乱了。您是指常量引用,而引用本身无法更改,还是完全锁定的,不可更改的对象?如果是前者,则默认情况下按值传递.Net中的引用。也就是说,您将获得参考的副本。因此,我们基本上具有与值类型相同的情况。如果实施者需要原始参考,他们可以自己保留它。

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


1
我因对这个问题的误解而拒绝了你。这不是要在调用后更改值,而是要在调用后更改它。
中午

2
@silky-我没有误解这个问题。我也在功能内谈论。我的意思是发送给函数是一个怪异的限制,因为它并没有真正停止任何事情。如果有人忘记了原始参数的更改,他们很可能会忘记更改的副本。
Joel Coehoorn

1
我不同意。仅提供注释以解释下降投票。
中午丝绸

DateTime仅具有只读字段访问,但是它仍然很有趣。它有许多返回新实例的方法。许多功能语言避免使用具有副作用的方法,而更喜欢不可变的类型,我认为这使代码更易于遵循。
Devin Garner

DateTime还是一个值类型,而不是引用类型。
Joel Coehoorn 2015年

5

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

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和其他此类规则方面,警惕性和整洁的代码是关键。


那实际上很聪明。我还要注意您可以同时继承多个接口-但您只能继承一个类。
Natalie Adams

这很有帮助...并且以相同的方式缓解了C#中缺少它的烦恼。感谢
Jimmyt1988 2015年

3

如果您经常遇到这样的麻烦,则应考虑“匈牙利应用”。好种类,而不是坏种类。尽管这通常不会尝试表示方法参数的常数(这太不寻常了),但是肯定没有什么可以阻止您在标识符名称前加上一个额外的“ c”。

现在,对于所有渴望猛烈按下downvote按钮的人们,请阅读以下有关这些主题的专家的意见:


请注意,您不需要命名约定即可强制执行此操作。事实发生后,您始终可以使用分析工具来完成此操作(虽然效果不佳,但尽管如此)。我相信Gendarme(mono-project.com/Gendarme)对此有规定,也许还有StyleCop / FxCop。
中午

很容易在解决方案上进行最糟糕的尝试(失败)。因此,现在您的参数不仅是可写的,而且还会因未更改而向您撒谎,从而使您失败了。
Rick O'Shea '18

到目前为止,有两个疼痛的SO用户可能会更糟。
汉斯·帕森特

2

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

用法1要求更改呼叫者定义。此用法仅用于测试“ TestCalled”功能代码的正确性。在发行版/构建中,您不应使用它。因为在大规模情况下,数学运算可能会过度转换,从而使您的代码运行缓慢。我不会使用它,但仅出于演示目的,我已将其发布。

我建议使用的用法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;
  }

}

如果ReadOnly是struct且VT既可以是struct又可以是class更好,那样的话,如果VT是struct,那么value将通过value传递,如果它是class,则value将通过引用传递,并且如果您希望它像java的最终运算符ReadOnly应该是隐式的。
Tomer Wolberg '18

0

如果将struct传递到方法中,除非ref传递了它,否则它不会被传递到的方法更改。所以从这个意义上讲,是的。

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

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

唯一可以更改外部数据的情况是,如果您传入一个类并更改其属性之一,或者您使用ref关键字传递了一个值。您概述的情况都不会。


除了关于不变性的评论外,我将+1 ...拥有一个不变类型将无法实现他所寻找的目标。
亚当·罗宾逊

当然可以,默认情况下。不通过引用传递的不可变类型将确保方法外部的值不变。
大卫·莫顿

1
是的,但他的示例是关于更改方法内部的值的。在他的示例代码中,x是一个Int32,它是不可变的,但仍然可以写x++。也就是说,他正在尝试防止重新分配参数,该参数与参数值的可变性正交。
itowlson '02

1
@silky Kinda奇怪地否决了三个被误解为同一问题的答案。这个问题被误解了可能不是读者的错。你知道,当男人与第三任妻子离婚时,这通常不是妻子的错。
大卫·莫顿

2
@David:这是投票系统的目的。依相关性排序。我不明白为什么它应该引起您的注意。
中午丝绸
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.