【问题标题】:Why can we not set properties of properties?为什么我们不能设置属性的属性?
【发布时间】:2013-12-17 17:23:12
【问题描述】:

我经常发现自己想做一些类似的事情:

Form form = new Form();
form.ClientSize.Width = 500;

当然编译器现在会抱怨这段代码无效,因为 ClientSize 是一个属性,而不是一个变量。

我们可以通过设置整个 ClientSize 来解决这个问题:

form.ClientSize = new Size(500, ClientSize.Height);

或者,一般来说:

Size s = form.ClientSize;
s.Width = 500;
form.ClientSize = s; //only necessary if s is a value-type. (Right?)

但这一切看起来都是不必要的和混淆的。为什么编译器不能为我做这个?当然,我问的是一般情况,可能涉及更深层次的属性,而不仅仅是上面的平凡示例

基本上,我在问为什么没有语法糖来将form.ClientSize.Width = 500 行翻译成上面的代码。这仅仅是一个尚未实现的功能吗,是为了避免堆叠来自不同 getter 和 setter 的副作用,在未定义其中一个 setter 时防止混淆,还是有完全不同的原因这不存在?

【问题讨论】:

  • @AndyT 我不太明白。你是说在这种特殊情况下标准库是邪恶的(因为 Size 是一个结构,而且绝对是可变的)?
  • @JSQuareD 整件事并不邪恶。这种特殊的类型是,是的。
  • @JSQuareD:有些人想假装 .NET 宇宙中的一切都是一个对象,并将任何行为不一样的东西视为邪恶。暴露场结构只是用胶带粘在一起的一堆变量,而不是一个对象。如果一个人想要是一堆用胶带粘在一起的变量,我认为最好使用暴露场结构(并将其视为一堆用胶带粘在一起的变量)比使用更不方便的数据类型并假装它是一个假装是一堆粘在一起的变量的对象。
  • @JSQuareD:顺便说一下,.NET 中的 any 结构类型只是一堆粘在一起的字段,但结构可以尝试表现得更像对象。可变结构不能很好地表现得像对象,而试图表现得像对象的可变结构通常是有问题的,但这意味着将是可变的结构通常不应该' t 假装是对象。如果一个结构不打算伪装成一个对象,那么公开(可变)公共字段是让这种缺乏伪装清晰的好方法。

标签: c# properties language-design


【解决方案1】:

为什么编译器不能为我做这个?

可以。事实上,如果你在 VB 中编程,它会做到这一点。 C# 编译器不这样做,因为它通常遵循按你说的做的哲学;它不是一种试图猜测你想做什么然后去做的语言。如果你告诉它做一些愚蠢的事情,它只会让你做一些愚蠢的事情。现在这种特殊情况是错误的常见来源,并且鉴于这些确切的语义几乎可以肯定是错误,所以它确实会导致错误,因为这样很好。

C# 程序员学会依赖 C# 编译器,从不决定做你从未告诉它做的事情,当编译器猜错你想做的事情时,这可能会导致混乱和问题。

【讨论】:

    【解决方案2】:

    我相信您在这里对 .NET 有一个根本性的误解。您可以整天为 class 类型设置属性的属性,因为您正在修改 reference 的数据而不更改引用。以这段代码为例,它编译并运行良好:

    class Program
    {
        public class Complex1
        {
            public Complex2 Complex2Property { get; set; }
        }
    
        public class Complex2
        {
            public int IntProperty { get; set; }
        }
    
        static void Main( string[] args )
        {
            // You must create instances of all properties to avoid a NullReferenceException
            // prior to accessing said properties
            var complex1 = new Complex1();
            complex1.Complex2Property = new Complex2();
    
            // Set property of property
            complex1.Complex2Property.IntProperty = 7;
        }
    }
    

    我假设您的对象是 struct 或 value 类型。您不能对结构执行此操作的原因是结构是一种值类型 - 它是按值复制的,而不是引用。因此,如果我将上面的示例更改为使 Complex2 成为结构,则无法执行此行:

    complex1.Complex2Property.IntProperty = 7;
    

    因为属性是 get 方法的语法糖,它将通过 value 返回结构,这意味着结构的副本,而不是属性所包含的相同结构。这意味着我对该副本的更改根本不会影响原始属性的结构,除了修改我的数据副本(不是我的属性中的数据)之外什么也没做。

    至于为什么编译器不为你做这个?它绝对可以,但它不会,因为你永远不想真正这样做。修改您实际上并未重新分配给您的属性的对象的副本没有任何价值。对于不完全理解值与引用类型(包括我自己!)的开发人员来说,这种情况是一个常见错误,因此编译器会选择警告您您的错误。

    【讨论】:

    • 他是一个结构体 (Size) 而不是一个类,这就是为什么他是非法的。
    • 好点@NickGotch - 我没有意识到这一点。我会更新我的答案。
    • 知道了。但这仍然留下了一个问题,为什么编译器不为我们处理这些恶作剧,而不是让我们不得不跳过句法循环来修改这个属性。很明显,我确实想要修改 Complex2Property(或 ClientSize),我为什么要关心这是否意味着必须创建它的副本?
    • "The reason you can't do this for structs is that a struct is an immutable type" 不,结构并非都是不可变的。它们通常应该是不可变的,因为mutable structs are evil,但在这种情况下,OP 有一个 mutable class,其属性为 可变的结构。
    • K,它现在是对观察到的行为的正确解释,但它仍然没有真正回答所提出的问题,即为什么编译器不代表程序员应用演示的转换。跨度>
    【解决方案3】:

    对于允许myForm.ClientSize.Width = 500; 的编译器,需要满足以下两个条件之一:编译器必须假设预期的行为等同于:

    var temp = myForm.ClientSize;
    temp.Width = 500;
    myForm.ClientSize = temp;
    

    否则myForm 必须将名称ClientSize 与签名为:

    void actupon_ClientSize<TParam>(ref Rectangle it, ref TParam param);
    

    在这种情况下,编译器可以生成类似于

    的代码
    myForm.actupon_ClientSize<int>((ref Rectangle r, ref int dummy)=>r.Width = 500, ref someDummyIntvar);
    

    其中someDummyIntVar 将是int 类型的任意值[第二个ref 参数可以在不生成闭包的情况下将参数传递给lambda]。如果框架描述了一种标准的方式让对象像这样公开属性,它将使许多类型的编程更安全、更方便。不幸的是,不存在这样的功能,我也不希望任何未来版本的 .NET 包含它。

    关于第一次转换,在很多情况下它会产生预期的效果,但也有很多情况是不安全的。恕我直言,.NET 没有充分的理由不指定可以指示各种转换何时安全和不安全的属性,但它们需要它们从第一天开始就存在,并且因为负责 .NET 的程序员一直认为他们宁愿宣布可变结构是“邪恶的”,也不愿做任何使它们不邪恶的事情,我怀疑这也不会改变。

    【讨论】:

      猜你喜欢
      • 2011-07-24
      • 2014-09-22
      • 2021-04-11
      • 2013-09-25
      • 1970-01-01
      • 2018-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多