为什么我们不能设置属性的属性?

时间:2013-12-17 17:23:13

标签: c# properties language-design

我经常发现自己想要沿着这些方向做点什么:

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时产生混淆,或者是否存在完全不同的原因这不存在吗?

3 个答案:

答案 0 :(得分:2)

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

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;
    }
}

我假设您的对象是结构或值类型。你不能为结构化做这个的原因是结构是一个值类型 - 它被值复制,而不是引用。因此,如果我将上面的示例更改为使Complex2为结构,我就无法执行此行:

complex1.Complex2Property.IntProperty = 7;

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

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

答案 1 :(得分:2)

  

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

它可以。事实上,如果你使用VB进行编程,那就完全可以了。 C#编译器没有这样做,因为它通常采取做你告诉它做的事情的哲学;它不是一种试图猜测你想做什么的语言,而是这样做。如果你告诉它做一些愚蠢的事情,它只会让你做一些愚蠢的事情。现在这种特殊情况是一个常见的错误来源,鉴于这些确切的语义几乎肯定是一个错误,所以它确实会导致错误,因为它很好。

C#程序员学会依赖C#编译器从不决定做一些你从未告诉过它的事情,当编译器猜错了你想做的事情时,这可能是造成混乱和问题的原因。

答案 2 :(得分:1)

对于允许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);

其中someDummyIntVarint类型的任意值[第二个ref参数可以将参数传递给lambda而不生成闭包]。如果框架描述了像这样暴露属性的对象的标准方法,那么它将使许多类型的编程更安全和更方便。不幸的是,没有这样的功能,我也不希望将来的.NET版本包含它。

关于第一次转型,有很多情况会产生预期的效果,但也有许多不安全的情况。恕我直言,没有充分的理由说明为什么.NET不应该指定何时表明各种转换是否安全的属性,但是它们需要它们从第一天开始就存在,并且因为负责.NET的程序员一直认为他们宁愿宣称可变结构是“邪恶的”,而不是任何会使它们变得不邪恶的结构,我怀疑这种情况会发生变化。