89

我正在寻找与 Java 的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.
}
4

9 回答 9

64

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

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

readonly关键字只能用于字段。

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

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

编辑(2019 年 8 月 13 日):我将其加入以提高知名度,因为这是公认的并且在列表中最高。现在可以使用in参数了。有关详细信息,请参阅下面的答案

于 2010-02-26T02:24:18.700 回答
37

这现在可以在 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;
}

尝试修改时将显示以下错误,x或者y因为它们被标记为in

无法分配给变量“in long”,因为它是只读变量

用手段标记论点in

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

于 2018-01-02T21:30:56.853 回答
9

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

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

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

于 2011-12-20T18:09:10.933 回答
8

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

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

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

祝你好运 :-)

于 2010-02-26T04:05:42.363 回答
7

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

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

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

于 2010-02-26T02:23:25.247 回答
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 和其他此类规则。

于 2013-08-24T17:05:29.190 回答
3

我知道这可能有点晚了。但是对于仍在寻找其他方法的人来说,可能有另一种方法可以绕过 C# 标准的这个限制。我们可以编写包装类 ReadOnly<T> where T : struct。隐式转换为基类型 T。但仅显式转换为 wrapper<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;
  }

}
于 2016-04-30T14:20:28.123 回答
2

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

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

于 2010-02-26T03:06:33.397 回答
0

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

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

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

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

于 2010-02-26T02:15:18.227 回答