C#的只读("const"式)函数参数

Mar*_*rkP 17 c# const

来自C++背景,我习惯于将const关键字粘贴到函数定义中,以使对象以只读值传递.但是,我发现在C#中这是不可能的(如果我错了请纠正我).经过一些谷歌搜索后,我得出的结论是,制作只读对象的唯一方法是编写一个只有'get'属性并将其传入的接口.优雅,我必须说.

public interface IFoo
{
  IMyValInterface MyVal{ get; }
}

public class Foo : IFoo
{
  private ConcreteMyVal _myVal;

  public IMyValInterface MyVal
  {
    get { return _myVal; }
  }
}
Run Code Online (Sandbox Code Playgroud)

我会把它传递给:

public void SomeFunction(IFoo fooVar)
{
  // Cannot modify fooVar, Excellent!!
}
Run Code Online (Sandbox Code Playgroud)

这可以.但是,在我的其余代码中,我想正常修改我的对象.向接口添加"set"属性会破坏我的只读限制.我可以添加一个'set'属性Foo(而不是IFoo),但签名需要一个接口而不是一个具体的对象.我不得不做一些铸造.

// Add this to class Foo. Might assign null if cast fails??
set { _myVal = value as ConcreteMyVal; }

// Somewhere else in the code...
IFoo myFoo = new Foo;
(myFoo as Foo).MyFoo = new ConcreteMyVal();
Run Code Online (Sandbox Code Playgroud)

是否有更优雅的方式复制const或制作只读函数参数而不添加其他属性或函数?

Tim*_*mwi 8

我想你可能正在寻找一个涉及两个接口的解决方案,其中一个继承自另一个接口:

public interface IReadableFoo
{
    IMyValInterface MyVal { get; }
}

public interface IWritableFoo : IReadableFoo
{
    IMyValInterface MyVal { set; }
}

public class Foo : IWritableFoo 
{
    private ConcreteMyVal _myVal;

    public IMyValInterface MyVal
    {
        get { return _myVal; }
        set { _myVal = value as ConcreteMyVal; }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以声明参数类型"告诉"它是否计划更改变量的方法:

public void SomeFunction(IReadableFoo fooVar)
{
    // Cannot modify fooVar, excellent!
}

public void SomeOtherFunction(IWritableFoo fooVar)
{
    // Can modify fooVar, take care!
}
Run Code Online (Sandbox Code Playgroud)

这模仿了类似于C++中的constness的编译时检查.作为埃里克利珀正确地指出,这是一样的不变性.但作为一名C++程序员,我认为你知道这一点.

顺便说一句,如果在类中声明属性的类型ConcreteMyVal并分别实现接口属性,则可以实现稍微更好的编译时检查:

public class Foo : IWritableFoo 
{
    private ConcreteMyVal _myVal;

    public ConcreteMyVal MyVal
    {
        get { return _myVal; }
        set { _myVal = value; }
    }

    public IMyValInterface IReadableFoo.MyVal { get { return MyVal; } }
    public IMyValInterface IWritableFoo.MyVal
    {
        // (or use “(ConcreteMyVal)value” if you want it to throw
        set { MyVal = value as ConcreteMyVal; }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,setter只能在通过接口访问时抛出,而不能在通过类访问时抛出.

  • 是什么阻止客户将 IReadable 转换为 IWriteable 并随心所欲地书写? (2认同)
  • @Eric:什么阻止`Console.WriteLine` 格式化我的硬盘?除了它试图履行其合同之外,别无他物。 (2认同)
  • 我的观点是,这实际上不是使用类型系统作为*强制*而是作为*礼貌建议*.如果这是目标那么好,你已经完成了.但我和其他人经常处于礼貌建议不够的情况; 如果该值实际上需要提供*no*机制,通过可验证的类型系统的使用可以发生突变,那么这是不够的.这就是为什么你不能只是将数组转换为IEnumerable <T>并返回它的原因; 有人仍然可以改变阵列.如果那是OP所处的情况那么一个不可变的包装器对象就更好了. (2认同)
  • @MoneyOrientedProgrammer:我认为“const”是一个糟糕的功能,原因有很多;C# 旨在改进 C++,不复制这个不好的功能只是一个小小的改进。我从来不想要“此方法被限制通过此引用修改此对象的状态”这一功能。我想要的功能是*这个对象的状态没有明显改变*,这是一个非常不同的功能。这是 C# 应该添加的内容。 (2认同)
  • @MoneyOrientedProgrammer:C# 中的一个良好设计原则是,*生成值*的方法应该没有副作用,因为否则,如果您不想要该效果,就无法计算该值。您认为 const 可以防止方法产生副作用的观点是错误的,但很多人都相信这一点,这就是为什么在我看来 const 是一个设计得很糟糕的危险特性。 (2认同)
  • @MoneyOrientedProgrammer:我们遇到的问题是*可观察效果*和*实现细节效果*之间存在很大差异。例如,考虑标记为 const 的昂贵递归算法。我们不能*作为性能改进*在不删除 const 的情况下记忆类中的方法,因为根据定义,记忆化是一种副作用:它会改变所记住的内容。我们最终不得不跳过疯狂的圈子来保持常量的正确性。 (2认同)

use*_*444 7

最接近的等价物是in关键字。usingin使参数和输入参数并防止其在方法内部被更改。来自官方 C# 文档:

  • in- 指定该参数通过引用传递,但只能由被调用的方法读取
  • ref- 指定该参数通过引用传递,并且可以由被调用的方法读取或写入。
  • out- 指定该参数通过引用传递,并且必须由被调用的方法写入。

在此输入图像描述

  • `in` 不会阻止您修改引用类型(类),如问题中所示。对于可变结构,如果调用输入结构的任何方法,它将进行防御性克隆。 (2认同)