为什么 C # 不允许只读局部变量?

和同事就这个问题进行了一次友好的辩论。我们对此有一些想法,但想知道观众对此有什么看法?

29962 次浏览

我认为这是因为一个函数有一个只读变量可能永远不会被调用,可能有一些关于它超出范围的东西,什么时候需要调用?

Readonly 意味着实例变量只能在构造函数中设置。当在本地声明一个变量时,它没有实例(它只是在作用域中) ,并且构造函数不能触及它。

一个原因是没有 CLR 支持只读本地。Readonly 被翻译成 CLR/CLI initonly 操作码。此标志只能应用于字段,对局部。事实上,将其应用于本地很可能会产生无法验证的代码。

这并不意味着 C # 不能这样做。但是对于同一种语言结构,它会赋予两种不同的含义。用于局部变量的版本将没有 CLR 等效映射。

对于 Jared 的回答,它可能只是一个编译时特性——编译器会禁止你在初始声明之后写入变量(这必须包含赋值)。

我能看到它的价值吗?有可能,但说实话,不是很多。如果您不能很容易地判断一个变量是否会在方法的其他地方被赋值,那么您的方法就太长了。

值得一提的是,Java 有这个特性(使用 final修饰符) ,而且我很少看到 非常使用它,除了在使用 已经允许匿名内部类捕获变量的情况下——在使用 的情况下,它给我的印象是混乱而不是有用的信息。

我就是那个同事,而且不友好! (开玩笑的)

我不会删除这个特性,因为编写简短的方法更好。这有点像说你不应该使用线程,因为它们很难。把刀给我,让我为没有割伤自己负责。

就个人而言,我想要另一个“ var”类型的关键字,如“ inv”(不变量)或“ rvar”,以避免混乱。我最近一直在研究 F # ,发现这个永恒的东西很有吸引力。

从来不知道 Java 有这个。

我认为对于 C # 架构师来说,这是一个糟糕的判断。局部变量上的 readonly 修饰符有助于维护程序正确性(就像断言一样) ,并可能有助于编译器优化代码(至少在其他语言的情况下)。C # 现在不允许使用这个特性,这是另一个争论点,即 C # 的一些“特性”仅仅是其创建者个人编码风格的强制执行。

我希望局部 只读变量的方式与我希望局部 康斯特变量的方式相同。但它没有其他议题那么重要。
也许它的 优先权和 C # 设计者不让 (还没!)实现这个特性的原因是一样的。但是在将来的版本中支持本地只读变量应该很容易(并且向后兼容)。

我知道,这不能回答你的问题。无论如何,那些阅读这个问题的人可能会喜欢下面的代码。

如果你真的很担心在重写一个本地变量的时候弄砸了,而这个本地变量只应该被设置一次,而你又不想让它成为一个更全局可访问的变量,你可以这样做。

    public class ReadOnly<T>
{
public T Value { get; private set; }


public ReadOnly(T pValue)
{
Value = pValue;
}


public static bool operator ==(ReadOnly<T> pReadOnlyT, T pT)
{
if (object.ReferenceEquals(pReadOnlyT, null))
{
return object.ReferenceEquals(pT, null);
}
return (pReadOnlyT.Value.Equals(pT));
}


public static bool operator !=(ReadOnly<T> pReadOnlyT, T pT)
{
return !(pReadOnlyT == pT);
}
}

示例用法:

        var rInt = new ReadOnly<int>(5);
if (rInt == 5)
{
//Int is 5 indeed
}
var copyValueOfInt = rInt.Value;
//rInt.Value = 6; //Doesn't compile, setter is private

也许没有 rvar rInt = 5代码少,但它工作。

如果使用 C # 交互式编译器 csi,可以在 C # 中声明只读局部变量:

>"C:\Program Files (x86)\MSBuild\14.0\Bin\csi.exe"
Microsoft (R) Visual C# Interactive Compiler version 1.3.1.60616
Copyright (C) Microsoft Corporation. All rights reserved.


Type "#help" for more information.
> readonly var message = "hello";
> message = "goodbye";
(1,1): error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer)

还可以用 .csx脚本格式声明只读局部变量。

这是 c # 语言设计者的一个疏忽。F # 有 val 关键字,它是基于 CLR 的。没有理由 C # 不能有相同的语言特性。

使用 const关键字使只读变量。

参考资料: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/const

public class SealedTest
{
static void Main()
{
const int c = 707;
Console.WriteLine("My local constant = {0}", c);
}
}

C # 7设计团队简要讨论了 只读局部变量和参数的一个提案:

参数和局部变量可以被 lambdas 捕获,因此可以并发访问,但是没有办法保护它们不受共享共有状态问题的影响: 它们不能是只读的。

一般来说,大多数参数和许多局部变量在得到它们的初始值之后都不会被赋值。在它们上面允许 readonly 可以清楚地表达这个意图。

一个问题是,这个特性可能是一个“有吸引力的麻烦”。尽管“正确的做法”几乎总是使参数和局部变量只读,但这样做会使代码显著混乱。

部分缓解这种情况的一个想法是允许局部变量上的组合 readonly var 被缩减为 val 或类似的短语。更一般地说,我们可以简单地想出一个比已建立的 readonly 更短的关键字来表示 readonly-ness。

在 C # 语言设计报告中继续讨论。投票表示支持

能够使局部变量只读使得理解复杂的算法更加容易,因为它减少了运动部件的数量。

因为 C # 并不为非编译时间常量提供本地化,所以我使用隐式强制转换:

public readonly struct ReadonlyVar<T>
{
private readonly T value;


internal ReadonlyVar(T _value) => value = _value;


public static implicit operator T(ReadonlyVar<T> _readonly) => _readonly.value;


public override string ToString() => "" + value;
}


public static class ReadonlyExt
{
public static ReadonlyVar<T> Readonly<T>(this T _value) => new ReadonlyVar<T>(_value);
}

用法:

int y = 234;
var x = ( 9000 + y ).Readonly();
y = x;

它并不完美,因为可以为它分配另一个 ReadonlyVar,但这从未在无意中发生在我身上。