C# 为什么可变结构是“;邪恶”;?
在这里的讨论之后,我已经读了好几遍关于可变结构是“邪恶”的评论(就像在这个问题的答案中) C#中的易变性和结构的实际问题是什么 从哪里开始-p 对于报价来说总是很好的: 这是可变的另一个原因 值类型是邪恶的。试着总是 使值类型不可变 首先,您很容易丢失更改。。。例如,从列表中获取内容:C# 为什么可变结构是“;邪恶”;?,c#,struct,immutability,mutable,C#,Struct,Immutability,Mutable,在这里的讨论之后,我已经读了好几遍关于可变结构是“邪恶”的评论(就像在这个问题的答案中) C#中的易变性和结构的实际问题是什么 从哪里开始-p 对于报价来说总是很好的: 这是可变的另一个原因 值类型是邪恶的。试着总是 使值类型不可变 首先,您很容易丢失更改。。。例如,从列表中获取内容: Foo foo = list[0]; foo.Name = "abc"; 这有什么变化?没有什么有用的 与属性相同: myObj.SomeProperty.Size = 22; // the compiler
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;
不那么关键的是,存在规模问题;可变对象倾向于具有多个属性;然而,如果你有一个包含两个
int
的结构,一个字符串
,一个日期时间
和一个布尔
,你可以很快地消耗大量内存。对于一个类,多个调用方可以共享对同一实例的引用(引用很小)。这与结构无关(也与C#无关),但在Java中,当可变对象是哈希映射中的键时,可能会遇到问题。如果您在将它们添加到地图后更改它们,并且它更改了它的属性,那么可能会发生不好的事情。我不会说不好,但对于程序员来说,为了提供最大的功能,可变性往往是一种过度热情的表现。实际上,这通常是不需要的,这反过来又使接口更小、更容易使用、更难使用(=更健壮)
其中一个例子是竞争条件下的读/写和写/写冲突。因为写操作不是有效的操作,所以这些操作不能发生在不可变的结构中
,程序员只是认为它可能在未来。例如,更改日期根本没有意义。相反,根据旧日期创建新日期。这是一个廉价的操作,因此性能不是一个考虑因素。结构是值类型,这意味着它们在传递时会被复制 因此,如果你改变了一份副本,你只改变了那份副本,而不是原件,也不是可能存在的任何其他副本
如果您的结构是不可变的,那么通过值传递产生的所有自动副本都是相同的
如果您想更改它,您必须有意识地使用修改后的数据创建一个新的结构实例。(不是副本)可变数据有许多优点和缺点。百万美元的缺点是别名。如果相同的值在多个位置使用,并且其中一个位置更改了该值,则该值似乎已神奇地更改为其他使用该值的位置。这与比赛条件有关,但不完全相同 百万美元的优势有时是模块化。可变状态允许您对不需要了解的代码隐藏更改信息
详细讨论了这些权衡,并给出了一些示例。值类型基本上表示不变的概念。Fx,有一个数学值,如整数、向量等,然后能够修改它是没有意义的。这就像重新定义一个值的含义。与其更改值类型,不如指定另一个唯一的值。请考虑这样一个事实,即通过比较其属性的所有值来比较值类型。关键是,如果属性相同,那么它就是该值的相同通用表示形式 正如Konrad提到的,更改日期也没有意义,因为该值表示唯一的时间点,而不是具有任何状态或上下文依赖关系的时间对象的实例
希望这对你有意义。可以肯定的是,它更多地是关于您试图用值类型捕获的概念,而不是实际的细节 假设您有一个1000000个结构的数组。每个结构都代表一个权益,包含出价、出价(可能是小数)等内容,这是由C#/VB创建的 假设数组是在非托管堆中分配的内存块中创建的,以便其他一些本机代码线程能够并发访问该数组(可能是一些进行数学运算的高性能代码) 假设C#/VB代码正在侦听价格变化的市场反馈,该代码可能必须访问数组的某些元素(对于任何一种证券),然后修改某些价格字段 想象一下,这是每秒数十次甚至数十万次的 让我们面对事实,在这种情况下,我们确实希望这些结构是可变的,它们必须是可变的,因为它们被其他一些本机代码共享,所以创建副本是没有帮助的;它们必须是这样的,因为以这样的速率复制一个120字节的结构是愚蠢的,特别是当一个更新实际上可能只影响一两个字节时
Hugo就我个人而言,当我看代码时,以下内容看起来相当笨拙: data.value.set(data.value.get()+1) 而不仅仅是 data.value++;或data.value=data.value+1 数据封装在传递类时非常有用,并且您希望确保以受控方式修改值。然而,当您拥有公共set和get函数时,这些函数所做的仅仅是将值设置为传入的值,这与简单地传递公共数据结构相比有何改进 当我在类中创建私有结构时,我创建该结构是为了将一组变量组织到一个组中。我希望能够在类范围内修改该结构,而不是获取该结构的副本并创建新实例
对我来说,这会阻止有效使用用于组织公共变量的结构,如果我想要访问控制,我会使用一个类。还有一些其他情况可能会导致程序出现不可预测的行为
// 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[] 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<T> interface
IList<Mutable> 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);
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);
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));
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());
}
point.x = point.x + 1
point = Point(point.x + 1, point.y)