当前位置:  开发笔记 > 编程语言 > 正文

为什么可变结构"邪恶"?

如何解决《为什么可变结构"邪恶"?》经验,为你挑选了15个好方法。

在这里讨论了SO之后,我已经多次读过可变结构是"邪恶"的评论(就像这个问题的答案一样).

C#中可变性和结构的实际问题是什么?



1> trampster..:

结构是值类型,这意味着它们在传递时被复制.

因此,如果您更改副本,则只更改该副本,而不是原始副本,而不是可能存在的任何其他副本.

如果您的结构是不可变的,则通过值传递的所有自动副本将是相同的.

如果要更改它,则必须通过使用修改后的数据创建结构的新实例来有意识地执行此操作.(不是副本)


"如果您的结构是不可变的,那么所有副本都是相同的." 不,这意味着如果你想要一个不同的价值,你必须有意识地制作一份副本.这意味着你不会因为修改原件而修改副本.
@Lucas我认为你在谈论一种不同类型的副本我正在谈论由于价值传递而产生的自动副本,你的"有意识制作的副本"是故意的,你没有错误地将它弄错了并不真正复制它包含不同数据的故意新时刻.
@Lucas:制作一个结构的副本,修改它,并以某种方式思考一个正在修改原始的危险(当一个人正在编写一个结构字段的事实使*自我明显*事实上只有一个人的副本)与持有类对象作为保存其中包含的信息的手段的人相比,将改变对象以更新其自己的信息并且在此过程中损坏由其他对象持有的信息的危险相比似乎相当小.
你的编辑(16个月后)使它更清晰一点.我仍然支持"(不可变的结构)意味着你不会被抓到修改副本,认为你正在修改原始",但是.
第3段听起来错误或不清楚.如果您的结构是不可变的,那么您将无法修改其字段或任何副本的字段._"如果你想改变它,你必须..."_那也是误导,你不能有意识地或无意识地改变**_ever_.创建一个新实例,除了具有相同的数据结构之外,您想要的数据与原始副本无关.

2> Marc Gravell..:

从哪里开始;-p

Eric Lippert的博客总是对报价有好处:

这是可变值类型是邪恶的另一个原因.尝试始终使值类型不可变.

首先,您很容易丢失更改...例如,从列表中获取内容:

Foo foo = list[0];
foo.Name = "abc";

这改变了什么?什么都没有用......

与属性相同:

myObj.SomeProperty.Size = 22; // the compiler spots this one

强迫你这样做:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

不太重要的是,存在尺寸问题; 可变对象往往具有多个属性; 但如果你有一个带有两个ints,a string,a DateTime和a的结构bool,你可以很快地烧掉大量的内存.对于类,多个调用者可以共享对同一实例的引用(引用很小).


@Konrad:myObj.SomeProperty.Size = 22会修改myObj.SomeProperty的COPY.编译器可以帮助您避免出现明显的错误.它不允许使用++.
@Konrad - 少一个间接它应该工作; 它是"改变一个只存在于堆栈上的瞬态值并且即将蒸发成虚无"的东西的值,这是被阻止的情况.
是的,但编译器只是这样愚蠢.不允许赋值给属性结构成员是IMHO一个愚蠢的设计决定,因为**允许`++`运算符.在这种情况下,编译器只是编写显式赋值本身而不是匆匆忙忙地编程.
@Marc Gravell:在前一段代码中,你最终会得到一个名为"abc"的"Foo",其他属性是List [0]的属性,而不会干扰List [0].如果Foo是一个类,则需要克隆它然后更改副本.在我看来,值类型与类别区别的一个大问题是"."的使用.运营商有两个目的.如果我有我的druthers,班级可以同时支持"." 和" - >"表示方法和属性,但是"."的正常语义.属性是创建一个修改了适当字段的新实例.

3> Konrad Rudol..:

我不会说邪恶,但可变性通常是程序员过度使用以提供最大功能的标志.实际上,这通常是不需要的,反过来又会使界面更小,更易于使用并且更难以使用错误(更强大).

其中一个例子是竞争条件下的读/写和写/写冲突.这些不可能在不可变结构中出现,因为写入不是有效操作.

此外,我声称几乎从不需要可变性,程序员只是认为可能在未来.例如,更改日期根本没有意义.而是根据旧日期创建一个新日期.这是一种廉价的操作,因此性能不是考虑因素.


就像我尊重Eric Lippert一样,他不是上帝(或者至少还没有).您链接到的博客文章和上面的帖子是使结构不可变的合理论据当然,但它们实际上非常弱,因为**从不**使用可变结构.不过,这篇文章是+1.
在C#中进行开发,您通常需要随时可变 - 尤其是使用您的业务模型,您希望流式传输等与现有解决方案一起顺利运行.我写了一篇关于如何使用可变和不可变数据的文章,解决了大多数可变性问题(我希望):http://rickyhelgesson.wordpress.com/2012/07/17/mutable-or-immutable-in-a-平行世界/
@StephenMartin:封装单个值的结构通常应该是不可变的,但结构是迄今为止封装固定的独立但相关的变量集合(如点的X和Y坐标)的最佳介质,它们没有"身份"作为组.用于**目的的结构通常应将其变量公开为公共字段.我会考虑这样一个概念:为了这样的目的而使用类而不是结构更合适是完全错误的.不可变类通常效率较低,可变类通常具有可怕的语义.
@StephenMartin:例如,考虑一个应该返回图形变换的六个`float`组件的方法或属性.如果这样的方法返回带有六个组件的exposed-field结构,很明显修改结构的字段不会修改从中接收它的图形对象.如果这样的方法返回一个可变类对象,可能更改其属性将更改底层图形对象,也许它不会 - 没有人真正知道.

4> JE42..:

可变结构不是邪恶的.

在高性能环境下,它们是绝对必要的.例如,当缓存行和/或垃圾收集成为瓶颈时.

我不会在这些完全有效的用例"邪恶"中使用不可变结构.

我可以看出C#的语法无法区分值类型或引用类型成员的访问,所以我更喜欢不可变结构,强制不可变结构,而不是可变结构.

然而,我不是简单地将不可变结构标记为"邪恶",而是建议采用该语言并倡导更有帮助和建设性的经验法则.

例如:"结构是值类型,默认情况下是复制的.如果您不想复制它们,则需要引用""首先尝试使用只读结构".


我还假设如果想要将一组固定的变量与管道胶带一起固定,这样它们的值可以单独处理或存储,也可以作为一个单元处理,因此要求编译器固定固定的一组更有意义.变量一起(即用公共字段声明`struct`)而不是定义一个可以使用的类,笨拙地实现相同的目的,或者为结构添加一堆垃圾以使它模拟这样的类(而不是而不是让它像一组与胶带粘在一起的变量,这首先是人们真正想要的)

5> supercat..:

具有公共可变字段或属性的结构不是邪恶的.

改变"this"的结构方法(与属性设置者不同)有点邪恶,只是因为.net不提供区分它们的方法.不改变"this"的struct方法即使在只读结构上也应该是可调用的,而不需要防御性复制.修改"this"的方法在只读结构上根本不应该是可调用的.由于.net不希望禁止不修改"this"的struct方法在只读结构上调用,但不希望允许只读结构发生变异,因此它会在读取时防御性地复制结构唯一的背景,可以说是两个世界中最糟糕的.

尽管在只读上下文中处理自变异方法存在问题,但是可变结构通常提供远远优于可变类类型的语义.考虑以下三种方法签名:

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

对于每种方法,请回答以下问题:

    假设该方法不使用任何"不安全"代码,它可能会修改foo吗?

    如果在调用方法之前不存在对'foo'的外部引用,那么之后是否存在外部引用?

回答:

问题1 ::
Method1()(明确意图)
Method2():是(明确意图)
Method3():是(不确定意图)
问题2
Method1()::否
Method2():否(除非不安全)
Method3():是

Method1不能修改foo,也永远不会得到引用.Method2获得了对foo的短暂引用,它可以使用任何顺序修改foo的字段,直到它返回,但它不能持久保存该引用.在Method2返回之前,除非它使用不安全的代码,否则可能由其'foo'引用构成的任何和所有副本都将消失.与Method2不同,Method3获得了对foo的混合可引用的引用,并且不知道它可以用它做什么.它可能根本不会改变foo,它可能会改变foo然后返回,或者它可能会将foo引用到另一个线程,这个线程可能会在某个任意的未来时间以任意方式改变它.限制Method3可能对传递给它的可变类对象执行操作的唯一方法是将可变对象封装到只读包装器中,这是一种丑陋且繁琐的操作.

结构数组提供了精彩的语义.给定Rectangle类型的RectArray [500],很明显且如何将元素123复制到元素456然后稍后将元素123的宽度设置为555,而不会干扰元素456."RectArray [432] = RectArray [321] ] ;;; RectArray [123] .Width = 555;".知道Rectangle是一个带有名为Width的整数字段的结构,它将告诉所有人需要知道上述语句.

现在假设RectClass是一个与Rectangle具有相同字段的类,并且想要对RectClass类型的RectClassArray [500]执行相同的操作.也许该数组应该包含500个预先初始化的可变引用,这些引用是可变的RectClass对象.在这种情况下,正确的代码将类似于"RectClassArray [321] .SetBounds(RectClassArray [456]); ...; RectClassArray [321] .X = 555;".也许假设数组包含不会改变的实例,因此正确的代码更像是"RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass(RectClassArray [321] ]); RectClassArray [321] .X = 555;" 要知道应该做什么,人们必须知道更多关于RectClass(例如它是否支持复制构造函数,复制方法等)以及数组的预期用法.没有使用结构那么干净.

可以肯定的是,遗憾的是,除了数组之外的任何容器类都没有很好的方法来提供结构数组的干净语义.最好的人可以做,如果想要用例如字符串索引集合,可能会提供一个通用的"ActOnItem"方法,该方法将接受索引的字符串,泛型参数和将传递的委托通过引用泛型参数和集合项.这将允许几乎与struct数组相同的语义,但除非可以追求vb.net和C#人员提供一个很好的语法,即使它是合理的性能,代码也会变得笨拙(传递一个通用参数会允许使用静态委托,并避免任何创建任何临时类实例的需要).

就个人而言,我对仇恨Eric Lippert等人感到不满.关于可变值类型的喷射.它们提供比在各处使用的混杂引用类型更清晰的语义.尽管.net支持值类型存在一些限制,但在许多情况下,可变值类型比任何其他类型的实体更适合.


这个答案比一些最高投票的答案更具洞察力.对于可变值类型的论证依赖于混合别名和变异时"你期望"的概念,这是荒谬的.无论如何,这是一件可怕的事情*!
@Ron Warholic:BTW,我希望*能够说"form.Bounds.X = 10;" 让它只是工作,但系统没有提供任何干净的方式这样做.将值类型属性公开为接受回调的方法的约定可以提供比使用类的任何方法更清晰,有效且可确认正确的代码.
@supercat:谁知道,也许他们为C#7谈论的ref-return功能可能会涵盖那个基础(我实际上没有详细看过它,但它表面上听起来很相似).

6> Morten Chris..:

值类型基本上代表不可变的概念.Fx,有一个数学值,如整数,向量等没有意义,然后能够修改它.这就像重新定义价值的含义一样.而不是更改值类型,分配另一个唯一值更有意义.考虑通过比较其属性的所有值来比较值类型的事实.关键是如果属性相同,那么它就是该值的通用表示.

正如Konrad所提到的那样,改变日期也没有意义,因为值表示唯一的时间点,而不是具有任何状态或上下文依赖性的时间对象的实例.

希望这对你有意义.更确切地说,它更多地是关于您尝试使用值类型而非实际细节捕获的概念.


好吧,我想他们可以使System.Drawing.Point不可变,但这将是一个严重的设计错误恕我直言.我认为点实际上是一种典型的价值类型,它们是可变的.除了真正早期编程101初学者之外,他们不会给任何人带来任何问题.
原则上我认为要点也应该是不可改变的,但如果它使得类型更难或更不优雅使用,那么当然也必须考虑.如果没有人想要使用它们,那么拥有支持最好的原则的代码结构是没有意义的;)
值类型对于表示简单的不可变概念很有用,但是外显字段结构是用于保存或传递小的固定相关但独立的值集(例如点的坐标)的最佳类型.这种值类型的存储位置封装了其字段的值,而不包含任何其他值.相反,可变引用类型的存储位置可以用于保持可变对象的状态,但也封装整个宇宙中存在于同一对象的所有其他引用的标识.
_"值类型基本上代表不可变的概念".不,他们没有.值类型变量的最古老和最有用的应用之一是`int`迭代器,如果它是不可变的则完全没用.我认为你将"值类型'编译器/运行时实现"与"键入类型的变量"混为一谈 - 后者对于任何可能的值都是可变的.

7> Sergey Teply..:

从程序员的角度来看,还有另外几个可能导致不可预测行为的案例.这里有几个.

    不可变值类型和只读字段

// Simple mutable structure. 
// Method IncrementI mutates current state.
struct Mutable
{
    public Mutable(int i) : this() 
    {
        I = i;
    }

    public void IncrementI() { I++; }

    public int I {get; private set;}
}

// Simple class that contains Mutable structure
// as readonly field
class SomeClass 
{
    public readonly Mutable mutable = new Mutable(5);
}

// Simple class that contains Mutable structure
// as ordinary (non-readonly) field
class AnotherClass 
{
    public Mutable mutable = new Mutable(5);
}

class Program
{
    void Main()
    {
        // Case 1. Mutable readonly field
        var someClass = new SomeClass();
        someClass.mutable.IncrementI();
        // still 5, not 6, because SomeClass.mutable field is readonly
        // and compiler creates temporary copy every time when you trying to
        // access this field
        Console.WriteLine(someClass.mutable.I);

        // Case 2. Mutable ordinary field
        var anotherClass = new AnotherClass();
        anotherClass.mutable.IncrementI();

        //Prints 6, because AnotherClass.mutable field is not readonly
        Console.WriteLine(anotherClass.mutable.I);
    }
}

    可变值类型和数组

假设有一个Mutable结构的数组,我们为该数组的第一个元素调用IncrementI方法.你对这次电话的期望是什么?它应该更改数组的值还是仅更改副本?

Mutable[] arrayOfMutables = new Mutable[1];
arrayOfMutables[0] = new Mutable(5);

// Now we actually accessing reference to the first element
// without making any additional copy
arrayOfMutables[0].IncrementI();

//Prints 6!!
Console.WriteLine(arrayOfMutables[0].I);

// Every array implements IList interface
IList listOfMutables = arrayOfMutables;

// But accessing values through this interface lead
// to different behavior: IList indexer returns a copy
// instead of an managed reference
listOfMutables[0].IncrementI(); // Should change I to 7

// Nope! we still have 6, because previous line of code
// mutate a copy instead of a list value
Console.WriteLine(listOfMutables[0].I);

因此,只要您和团队的其他成员清楚地了解您在做什么,可变结构就不是邪恶的.但是,当程序行为与预期行为不同时,存在太多极端情况,这可能导致难以产生和难以理解的错误.


应该发生什么,如果.net语言有更好的值类型支持,那么结构方法应该被禁止改变'this',除非它们被明确声明为这样做,并且这样声明的方法应该被禁止在只读中上下文.可变结构的数组提供了有用的语义,这些语义无法通过其他方式有效地实现.
这些都是可变结构会引起非常细微问题的很好的例子。我不会期望任何这种行为。为什么数组给您引用,但接口给您值呢?除了实时值(这是我真正希望的)之外,我会认为,至少是相反的方法:提供引用的接口;给出值的数组...
哦,我的...这使得可变的结构该死的邪恶!

8> ThunderGr..:

如果您曾使用C/C++这样的语言进行编程,那么结构可以用作可变的.只需用ref传递它们,周围就没有什么可以出错的.我发现的唯一问题是C#编译器的限制,在某些情况下,我无法强制使用对结构的引用而不是复制(就像结构是C#类的一部分时一样) ).

因此,可变结构不是邪恶的,C#使它们变得邪恶.我一直在C++中使用可变结构,它们非常方便直观.相比之下,C#让我完全放弃了作为类成员的结构,因为它们处理对象的方式.他们的便利花了我们的钱.



9> 小智..:

想象一下,你有一系列1,000,000个结构.每个结构代表一个与bid_price,offer_price(可能是小数)等东西的股权,这是由C#/ VB创建的.

想象一下,数组是在非托管堆中分配的内存块中创建的,这样一些其他本机代码线程就能够同时访问该数组(可能是一些高性能代码进行数学运算).

想象一下,C#/ VB代码正在收听价格变化的市场反馈,该代码可能必须访问数组的某些元素(无论哪种安全性),然后修改一些价格字段.

想象一下,这每秒要做几十甚至几十万次.

好吧,让我们面对事实,在这种情况下,我们确实希望这些结构是可变的,它们需要是因为它们被其他一些本地代码共享,所以创建副本不会有帮助; 它们需要是因为以这些速率制作一些120字节结构的副本是疯狂的,特别是当更新实际上只影响一两个字节时.

雨果


我不确定为什么有些人认为这些东西应该是类对象.如果所有数组槽都填充了引用不同的实例,则使用类类型将为内存需求添加额外的12或24个字节,并且对类对象引用数组的顺序访问比顺序访问要慢得多.一系列结构.
是的,但在这种情况下,使用结构的原因是,外部约束(通过本机代码使用的那些)强加于应用程序设计.您描述的关于这些对象的其他所有内容都表明它们应该显然是C#或VB.NET中的类.

10> Luis Masuell..:

如果您坚持使用什么结构(在C#,Visual Basic 6,Pascal/Delphi,C++结构类型(或类)中它们不用作指针时),您会发现结构不超过复合变量.这意味着:您将在一个通用名称(引用成员的记录变量)下将它们视为一组压缩变量.

我知道这会让很多人习惯于OOP而感到困惑,但如果使用得当,这并不足以说明这些事情本来就是邪恶的.有些结构是不可改变的(这是Python的情况namedtuple),但它是另一种需要考虑的范例.

是的:结构涉及大量内存,但通过执行以下操作并不会更多内存:

point.x = point.x + 1

相比:

point = Point(point.x + 1, point.y)

在不可知的情况下,内存消耗将至少相同,甚至更多(尽管这种情况对于当前堆栈而言是暂时的,具体取决于语言).

但是,最后,结构是结构,而不是对象.在POO中,对象的主要属性是它们的身份,大多数时间不超过其内存地址.Struct代表数据结构(不是适当的对象,因此无论如何它们都没有标识),并且可以修改数据.在其他语言中,记录(而不是结构,如Pascal的情况)是单词并且具有相同的目的:只是一个数据记录变量,旨在从文件中读取,修改并转储到文件中(这是主要的)使用和,在许多语言中,您甚至可以在记录中定义数据对齐,而对于正确调用的对象则不一定如此.

想要一个好榜样吗?结构用于轻松读取文件.Python有这个库,因为它是面向对象的并且不支持结构,它必须以另一种方式实现它,这有点难看.实现结构的语言具有内置的功能.尝试使用Pascal或C等语言中的相应结构读取位图标头.这将很容易(如果结构正确构建并对齐;在Pascal中,您不会使用基于记录的访问,而是使用函数来读取任意二进制数据).因此,对于文件和直接(本地)内存访问,结构比对象更好.至于今天,我们已经习惯了JSON和XML,因此我们忘记了二进制文件的使用(并且作为副作用,结构的使用).但是:它们存在,并且有目的.

他们不是邪恶的.只是将它们用于正确的目的.

如果你考虑锤子,你会想要把螺丝当作钉子,找到螺丝更难以插入墙壁,这将是螺丝的错,它们将是邪恶的.



11> Doval..:

当某些东西可以变异时,它会获得一种认同感.

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

因为Person是可变的,它更自然地想到改变Eric的位置不是克隆埃里克,移动克隆,并破坏原有的.这两个操作都会成功改变内容eric.position,但一个比另一个更直观.同样地,通过Eric(作为参考)传递修改他的方法更为直观.给一个方法克隆Eric几乎总是令人惊讶.任何想要变异的人都Person必须记得要求参考,Person否则他们会做错事.

如果你使类型不可变,问题就会消失; 如果我无法修改eric,无论我收到eric还是克隆,都没有任何区别eric.更一般地说,如果一个类型的所有可观察状态都保存在以下成员中,则可以安全地传递值:

一成不变

参考类型

安全地通过价值

如果满足这些条件,则可变值类型的行为类似于引用类型,因为浅副本仍将允许接收器修改原始数据.

不可变的直观性Person取决于你想要做的事情.如果Person只是代表一关于一个人的数据,那就没有什么不直观的了; Person变量真正代表抽象,而不是对象.(在这种情况下,重命名它可能更合适PersonData.)如果Person实际上是对一个人进行建模,即使你已经避免了认为你正在修改的陷阱,那么不断创建和移动克隆的想法是愚蠢的.原本的.在这种情况下,简单地创建Person引用类型(即类)可能更自然.

当然,正如功能性编程告诉我们的那样,让一切都变得一成不变(没有人可以暗中坚持引用eric并改变他),但是因为在OOP中这不是惯用的,所以对于任何与你一起工作的人来说,它仍然是不直观的.码.


关于*身份*的观点是好的; 值得注意的是,只有当存在多个引用时,身份才是相关的.如果`foo`在宇宙中的任何地方拥有对其目标的唯一引用,并且没有任何东西捕获该对象的identity-hash值,那么变换字段`foo.X`在语义上等同于使`foo`指向一个新对象,即就像它之前提到的那个,但是`X`保持所需的值.对于类类型,通常很难知道是否存在多个引用,但结构很容易:它们不容易.

12> Bombe..:

它与结构没有任何关系(也没有与C#有关),但在Java中,当它们是例如哈希映射中的键时,可能会遇到可变对象的问题.如果在将它们添加到地图后更改它们并更改其哈希码,则可能会发生恶意事件.


如果您在地图中使用类作为键,那也是如此.

13> 小智..:

就个人而言,当我查看代码时,以下内容对我来说非常笨重:

data.value.set(data.value.get()+ 1);

而不是简单

data.value ++; 或data.value = data.value + 1;

传递类时,数据封装很有用,并且您希望确保以受控方式修改值.但是,当你有公共设置和获取功能时,只需将值设置为传递的值,这比仅仅传递公共数据结构有什么改进?

当我在类中创建私有结构时,我创建了该结构以将一组变量组织到一个组中.我希望能够在类范围内修改该结构,而不是获取该结构的副本并创建新实例.

对我来说这可以防止有效使用用于组织公共变量的结构,如果我想要访问控制,我会使用一个类.



14> Ramesh Kadam..:

Eric Lippert先生的例子有几个问题.人为地说明了结构被复制的意义以及如果你不小心可能会出现问题.看一下这个例子,我认为它是一个糟糕的编程习惯,而不是结构或类的问题.

    结构应该只有公共成员,不应该要求任何封装.如果它确实那么它真的应该是一个类型/类.你真的不需要两个结构来说同样的事情.

    如果你有一个封闭结构的类,你可以在类中调用一个方法来改变成员结构.这就是我作为一个良好的编程习惯所做的事情.

适当的实施如下.

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

看起来这是编程习惯的问题,而不是struct本身的问题.结构应该是可变的,这就是想法和意图.

变化的结果表现如预期:

1 2 3按任意键继续...


设计小的不透明结构以表现得像不可变的类对象是没有错的; MSDN指南是合理的*当一个人试图制作一个像对象*一样的东西时.在某些情况下,结构是合适的,其中一个人需要像对象一样的轻量级东西,并且在需要一堆变量与管道胶带粘在一起的情况下.然而,出于某种原因,许多人没有意识到结构有两种截然不同的用法,而适用于一种结构的指导方针则不适用于另一种.

15> Andru Luvisi..:

可变数据有很多优点和缺点。百万美元的劣势是混叠。如果在多个位置使用了相同的值,并且其中一个更改了它,那么它似乎已经神奇地更改为正在使用它的其他位置。这与比赛条件有关,但并不完全相同。

有时,数百万美元的优势是模块化。可变状态可以让您从不需要知道的代码中隐藏变化的信息。

口译员的技巧对这些折衷进行了详细介绍,并给出了一些示例。

推荐阅读
和谐啄木鸟
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有