C#中的“常量正确性”


79

const正确性的重点是能够提供用户无法更改或删除的实例的视图。编译器通过指出何时从const函数中破坏constness或尝试使用const对象的非const函数来支持此功能。因此,无需复制const方法,是否可以在C#中使用具有相同目的的方法?

我知道不变性,但这并没有真正延续到容器对象上,仅举一个例子。



const_cast用C#实现呢?
kerem 2012年

Answers:


64

我也多次遇到这个问题,最终使用接口。

我认为重要的一点是,不要认为C#是任何形式,甚至是C ++的演变。它们是两种共享几乎相同语法的不同语言。

我通常通过定义类的只读视图在C#中表达“常量正确性”:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}

12
可以,但是如果您的字段之一是列表或富类型,该怎么办。您的解决方案变得非常复杂。
马特·克鲁克香克

12
@陷阱:这就是问题的全部。返回内部集合是一种不好的做法,因为外部世界可以修改您的内部,在C ++中,使用const可解决该问题:您提供了集合的恒定视图(不能添加/删除元素,仅提供其内部的恒定视图元素),因此是安全的(也是常见的习惯用语)。无需制定接口或其他技巧即可避免外部代码更改您的内部构造。
大卫·罗德里格斯(DavidRodríguez)-dribeas

9
@陷阱,我同意该机制是对开发的一种帮助,它确实包括检测开发人员的错误,无论是否经验丰富。我不同意在多个方面必须编写只读接口是更好的解决方案。第一点是,每当您使用C ++编写类时,您都在隐式定义常量接口:声明的成员子集const,而无需定义单独的接口并需要运行时调度。也就是说,该语言提供了一种实现编译时const接口的简单方法。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2010年

10
重要的是要注意const-ness设计的一部分,它声明意图与编写外部const接口一样清晰。此外,如果必须手动处理,则提供const接口可能是一项复杂的任务,并且可能需要编写与复合类型中存在的类几乎一样多的接口。这将我们带到您的最后一个观点:“由于忘记添加const而弄乱了内容”。与const为每个类编写只读接口相比,习惯于在各处添加内容(开发成本很小)要容易得多。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2010年

9
我真正喜欢C#的原因之一是,它没有像Java一样抛出C ++的所有实用功能。Java尝试使用接口进行所有操作,这是一场灾难。证明在布丁里。随着时间的流逝,许多功能被“反向”添加到Java中,这些功能最初被称为异端。基于接口的设计就可以了,但是如果过分极端,可能会不必要地使程序的体系结构复杂化。您最终将花费更多的时间编写样板代码,而花费较少的时间来完成有用的工作。
kgriffs 2011年

29

为了获得const-craziness(或函数式编程术语的纯净性)的好处,您将需要设计类以使其不可变,就像c#的String类一样。

这种方法比仅将对象标记为只读要好得多,因为使用不可变的类,您可以在多任务环境中轻松传递数据。


8
但是不变性并不能真正扩展到复杂的对象,或者是吗?
tenpn

8
我认为如果您的对象是如此复杂以至于不可能实现不变性,那么您将有一个很好的重构对象。
Jim Burger

2
我认为这是小组中最好的。不变的对象使用得很少。

3
不仅如此,在某些情况下您确实希望拥有更改的对象(对象的确发生更改!),但在大多数情况下仍提供只读视图。不可变的对象意味着,每当需要进行更改(发生更改)时,除了更改外,还需要使用所有相同的数据创建一个新的对象。考虑一间有教室,有学生的学校...您是否想在学生的出生日期和年龄变化时创建一所新学校?还是可以仅更改学生级别的年龄,房间级别的学生,或者是学校级别的房间的年龄?
大卫·罗德里格斯(DavidRodríguez)-dribeas

8
@tenpn:正确完成后,不变性实际上可以很好地扩展。真正有用的一件事是将Builder类用于大型不可变类型(Java和.NET定义了StringBuilder该类,这只是一个示例)。
康拉德·鲁道夫

24

我只是想为您指出,许多System.Collections.Generics容器都有一个AsReadOnly方法,它将为您提供不可变的集合。


4
即使ReadOnlyCollection <T>不是,T仍然是可变的。
arynaq

4

C#没有这种功能。您可以按值或引用传递参数。引用本身是不可变的,除非您指定ref修饰符。但是引用的数据不是一成不变的。因此,如果要避免副作用,则需要小心。

MSDN:

传递参数


好吧,我想这就是问题的根源:避免没有const结构的副作用的最佳方法是什么?
tenpn

不幸的是,只有不可变的类型可以帮助它。您可以看一下Spec#-有一些有趣的编译时检查。
aku

3

接口是答案,并且实际上比C ++中的“ const”更强大。const是解决“ const”定义为“不设置成员或不调用设置成员的东西”的问题的一刀切解决方案。在许多情况下,这是保持一致性的好捷径,但并非所有情况都如此。例如,考虑一个函数,该函数根据某些成员计算值,但也缓存结果。在C ++中,这被认为是非常量,尽管从用户的角度来看,它本质上是常量。

接口使您可以更灵活地定义要从类中提供的功能的特定子集。想要保持不变?只需提供没有任何变异方法的接口即可。是否要允许设置某些东西,而不允许设置其他东西?仅提供带有这些方法的接口。


15
并非100%正确。允许C ++ const方法对标记为的成员进行突变mutable
康斯坦丁

3
很公平。而且const-casting使您摆脱了const-ness。两者都暗示即使C ++设计人员也意识到“一刀切”才是真正的“一刀切”。
优厚

8
C ++的优点是,在大多数情况下您都可以使用const,并且可以为其他人实现接口。现在,除了const之外,C ++还具有mutable关键字,可以将其应用于属性,如数据缓存或锁定机制(互斥体等)。const不是'不会更改任何内部属性),而是不会更改从外部感知到的对象状态。也就是说,在调用const方法之前和之后对对象的任何使用都会产生相同的结果。
大卫·罗德里格斯(DavidRodríguez)-dribeas

9
在C ++中舍弃const来改变事物是不可靠的行为(鼻恶魔)。const_cast用于与在逻辑上未标记为const的传统代码进行接口。
jk。

3

与其他一些人达成共识后,他们会考虑使用在构造函数中初始化的只读字段来创建不可变对象。

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

或者,您也可以在属性上添加访问范围,即public get和protected set?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }

3
这些是不完全适合整个问题的不同方法。使用访问控制级别,您仅允许从更改派生类,在许多情况下,这将无法适当地模拟现实世界。老师不是从学生记录中获取信息,而是可能希望更改学生成绩,尽管学生无法更改成绩但可以阅读成绩……仅举一个简单的例子。
大卫·罗德里格斯(DavidRodríguez)-dribeas

2
  • 常量关键字可以用于编译时间常量如基本类型和字符串
  • 所述只读关键字可以用于运行时间常量如引用类型

只读的问题在于,它仅允许引用(指针)为常数。引用(指向)的内容仍可以修改。这是棘手的部分,但是没有办法解决。实现常量对象意味着使它们不公开任何可变的方法或属性,但这很尴尬。

另请参见有效的C#:50种改善C#的特定方法(第2项-首选只读而不是const)。

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.