【问题标题】:Modifying nested structs in c#在 C# 中修改嵌套结构
【发布时间】:2010-04-12 16:44:48
【问题描述】:

有人能告诉我为什么注释的代码行(前一个)不能编译吗?跟后面的那一行不一样吗?

public struct OtherStruct
{
    public int PublicProperty { get; set; }
    public int PublicField;

    public OtherStruct(int propertyValue, int fieldValue)
        : this()
    {
        PublicProperty = propertyValue;
        PublicField = fieldValue;
    }

    public int GetProperty()
    {
        return PublicProperty;
    }
    public void SetProperty(int value)
    {
        PublicProperty = value;
    }
}

public struct SomeStruct
{
    public OtherStruct OtherStruct { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        SomeStruct a = new SomeStruct();
        //a.OtherStruct.PublicProperty++;
        a.OtherStruct.SetProperty(a.OtherStruct.GetProperty() + 1);
    }
}

【问题讨论】:

    标签: c# struct


    【解决方案1】:

    SomeStruct.OtherStruct 是一个属性,返回一个值——它不是一个变量。这一行:

    a.OtherStruct.PublicProperty++;
    

    就像打电话:

    a.get_OtherStruct().PublicProperty++;
    

    因为表达式a.get_OtherStruct()是一个而不是一个变量,所以有点像这样做:

    OtherStruct tmp = a.get_OtherStruct();
    tmp.PublicProperty++;
    

    更改属性返回的OtherStruct副本PublicProperty 的值根本不会更改原始值。这几乎肯定不是你的意图。 C# 设计者预见到了这类问题,并设法在许多情况下禁止它。

    请注意,如果 OtherStruct 是一个引用类型(一个类),那么复制的将是 reference,而不是其中的值...所以更改 tmp.PublicProperty 会有所作为。请参阅我的article on reference and value types 了解更多信息。

    顺便说一句,像这样的可变结构通常是一个非常糟糕的主意。它们会导致各种问题和难以预测的代码。

    编辑:响应您的“答案”,这两行相同:a.OtherStruct 属性表达式不是赋值运算符或复合赋值运算符的目标。

    您可以争辩说您希望 C# 以允许这样做的方式定义(尽管我仍然不同意)但编译器 正确地实现了规范。有关详细信息,请参阅 C# 3.0 规范的第 10.7.2 节。

    【讨论】:

    • 我认为如果您使用一个显示值类型和引用类型之间差异的示例可能会更清楚。
    • @Jeff:我已经添加了一个段落和一个链接......希望可以解决问题。
    • 是的,我认为这有很大的不同。顺便说一句,这是一个很好的解释,只是认为主题可能会令人困惑,所以每一点都有帮助。干杯。
    • 请看下一个答案 - 我无法将其放入评论中。
    • 我明白你在说什么。在 OtherStruct 示例中,a.OtherStruct 被放置在堆栈上,从而产生一个副本。而在 SomeStruct 示例中,PublicProperty 的集合直接指向变量而不是副本。有道理,谢谢。
    【解决方案2】:

    很抱歉没有使用评论,我认为它不合适。 乔恩,这不是一个实际的实现,我只是想更深入地了解结构,所以不用担心我实现可变结构:)

    无论如何,我不确定你是否正确。考虑这段代码,它与第一个示例几乎相同:

    public struct SomeStruct
    {
        public int PublicProperty { get; set; }
        public int PublicField;
    
        public SomeStruct(int propertyValue, int fieldValue)
            : this()
        {
            PublicProperty = propertyValue;
            PublicField = fieldValue;
        }
    
        public int GetProperty()
        {
            return PublicProperty;
        }
        public void SetProperty(int value)
        {
            PublicProperty = value;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            SomeStruct a = new SomeStruct(1, 1);
            a.PublicProperty++;
            a.SetProperty(a.GetProperty()+1);
        }
    }
    

    现在,使用 ildasm 查看 msil,主要方法如下:

    .method private hidebysig static void Main(string[] args) cil managed

    {

    .entrypoint
    
    // Code size       45 (0x2d)
    
    .maxstack  3
    
    .locals init ([0] valuetype ConsoleApplication1.SomeStruct a)
    
    IL_0000:  nop
    
    IL_0001:  ldloca.s   a
    
    IL_0003:  ldc.i4.1
    
    IL_0004:  ldc.i4.1
    
    IL_0005:  call       instance void ConsoleApplication1.SomeStruct::.ctor(int32,
                                                                             int32)
    IL_000a:  nop
    
    IL_000b:  ldloca.s   a
    
    IL_000d:  dup
    
    IL_000e:  call       instance int32 
    

    ConsoleApplication1.SomeStruct::get_PublicProperty()

    IL_0013:  ldc.i4.1
    
    IL_0014:  add
    
    IL_0015:  call       instance void 
    

    ConsoleApplication1.SomeStruct::set_PublicProperty(int32)

    IL_001a:  nop
    
    IL_001b:  ldloca.s   a
    
    IL_001d:  ldloca.s   a
    
    IL_001f:  call       instance int32 ConsoleApplication1.SomeStruct::GetProperty()
    
    IL_0024:  ldc.i4.1
    
    IL_0025:  add
    
    IL_0026:  call       instance void ConsoleApplication1.SomeStruct::SetProperty(int32)
    
    IL_002b:  nop
    
    IL_002c:  ret
    

    }

    对于糟糕的格式,我深表歉意,我不知道如何使它看起来正常。无论如何,希望您能看到 main 方法中的最后两行代码实际上是相同的。

    因此,我认为,从上一篇文章来看,这一行:

    a.OtherStruct.PublicProperty++;
    

    实际上与它后面的行相同:

     a.OtherStruct.SetProperty(a.OtherStruct.GetProperty() + 1);
    

    因此在我看来第一行不能编译只是因为编译器不支持它,而不是因为它不合法。

    你怎么看?

    【讨论】:

    • 这应该被编辑到问题中,而不是作为答案添加......无论如何,我会适当地编辑我的答案。
    • 好的,下次记得。
    【解决方案3】:

    如果没有充分的理由,结构不应公开类似字段的读写属性。相反,他们应该直接公开字段。 归因于“可变结构”的大多数“问题”实际上是暴露读写属性的结构的问题。 在您的示例中,如果您只是简单地创建了@987654322 类型的成员OtherStruct @ 是一个字段而不是一个属性(即丢失 { get; set; } 那么嵌套结构访问就没有问题了。

    另请注意,人们对“可变结构”的一个“问题”与变异 this(类似字段的属性所做的事情)无关,源于以下代码中的事实:

    someStructType myThing = MyDataSupplier.GetSomeData(); myThing.someField = 某物;

    myThing 的更改不会渗透回MyDataSupplier,缺少如下代码:

    MyCollection[whatever] = myThing;

    我的反应是“自然地。知道someStructType 是一个带有字段someField 的结构就足以知道myThing.someField 可以被改变而不影响宇宙中的任何其他东西。”相比之下,如果要替换myClassType,并且myClassType 有一个可变成员(无论是字段还是属性),上面的代码“可能”干净地改变MyDataSupplier 中的数据,或者它可能不会,并且这样的修改可能会或可能不会破坏系统状态的其他方面。实际上,抱怨是一个人会有可预测的行为,这并不总是符合一个人想要做的事情,而不是有依赖于许多事情的语义,包括在某些情况下MyDataSupplier 的其他消费者。另请注意,如果someStructType 公开了读写属性而不是字段,则必须检查与该属性关联的代码以确定它是否会影响调用它的结构实例之外的事物。

    【讨论】:

      猜你喜欢
      • 2018-07-21
      • 1970-01-01
      • 1970-01-01
      • 2015-12-11
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 2014-10-12
      • 1970-01-01
      相关资源
      最近更新 更多