我在某处读到,在类中拥有公共属性比拥有公共成员更好。
这仅仅是因为抽象和模块化吗?还有其他重要原因吗?
属性访问由编译器转换为函数调用。对于没有备份存储的属性(例如
public string UserName { get; set; }
),与直接成员访问相比,性能开销是多少? (我知道它通常不会产生影响,但在我的某些代码中,属性被访问了数百万次。)
编辑1: 我对整数成员和 Properties 运行了一些测试代码,公共成员的速度大约是 Properties 的 3-4 倍。 (调试中的 ~57 毫秒与 ~206 毫秒以及发布中的 57 与 97 是最常见的运行值)。对于 1000 万次读取和写入,两者都足够小,不足以证明更改任何内容是合理的。
代码:
class TestTime1
{
public TestTime1() { }
public int id=0;
}
class TestTime2
{
public TestTime2() { }
[DefaultValue(0)]
public int ID { get; set; }
}
class Program
{
static void Main(string[] args)
{
try
{
TestTime1 time1 = new TestTime1();
TestTime2 time2 = new TestTime2();
Stopwatch watch1 = new Stopwatch();
Stopwatch watch2 = new Stopwatch();
watch2.Start();
for (int i = 0; i < 10000000; i++)
{
time2.ID = i;
i = time2.ID;
}
watch2.Stop();
watch1.Start();
for (int i = 0; i < 10000000; i++)
{
time1.id = i;
i = time1.id;
}
watch1.Stop();
Console.WriteLine("Time for 1 and 2 : {0},{1}",watch1.ElapsedMilliseconds,watch2.ElapsedMilliseconds);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.In.ReadLine();
}
}
连续运行测试20次,确保在Release构建中启用JIT优化:
Time for 1 and 2 : 47,66
Time for 1 and 2 : 37,42
Time for 1 and 2 : 25,36
Time for 1 and 2 : 25,25
Time for 1 and 2 : 27,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 26,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
Time for 1 and 2 : 25,25
是的,JITter that擅长内联属性访问器。性能不是问题,永远不应该考虑。
完全不用担心性能开销。它是如此之小,以至于您不应该考虑削弱类的封装性;这将是最糟糕的过早优化。
这仅仅是因为抽象和模块化吗?还有其他重要原因吗?
据我所知;这些理由本身就足够令人信服。但也许其他人会加入进来。
编译器将属性访问转换为函数调用。对于没有备份存储的属性(例如 public string UserName { get; set; }),与直接成员访问相比,性能开销是多少? (我知道它通常不会产生影响,但在我的某些代码中,属性被访问了数百万次。)
在生成的中间语言中,属性访问被转换为方法调用。然而,正如这个词所说,这只是一种中间语言:它被即时编译成其他语言。此转换步骤还涉及优化,例如内联简单方法,例如简单的属性访问器。
我希望(但您需要测试以确保)JITter 会处理此类访问器,因此应该没有性能差异。
它主要用于抽象目的(您可以稍后添加验证,而不会破坏现有代码或需要重新编译)。
即使使用自动属性,仍然有一个由编译器生成的支持字段,并将按此执行。
确保您使用 Ctrl-F5 而不是 F5 运行;否则,即使在发布模式下,调试器仍将附加并且某些优化可能无法正常工作。至少在我的机器上是这样的:F5 给出的结果与您发布的结果相似,而 Ctrl-F5 给出的结果相同。
1) 其用于封装原则,但其他 .NET 功能使用数据绑定等属性。
2)我不确定我是否同意这一点,我总是听说,如果属性是直接获取/设置,那么它与标准字段访问一样快 - 编译器会为您执行此操作。
更新:似乎两者兼而有之,编译为方法调用,但经过 JIT 优化。不管怎样,这种性能问题不会对您的代码产生有意义的影响。但是,请注意,有关实现属性的指导是使它们尽可能轻量级,调用者预计它们不会很昂贵。
在我发布this帖子后,我意识到它基本上是为了隐藏对象的内部运作。
我之前问过同样的问题。
我猜您正在使用 VS2008,正在使用 64 位操作系统并将编译设置为“任何 CPU”?如果是这样,x64 JIT 编译器不会内联属性。它们在 32 位上运行,这使得它们的性能与公共领域相同。
如果您想要一个需要属性但无法使用常规成员变量执行的操作的具体示例,请考虑继承:如果一个类使用公共成员,则该类的派生无法实现验证或其他 getter/setter 行为。他们坚持使用变量本身,如果他们想做一些不同的事情,他们必须 1) 忽略现有的成员变量并创建一个新属性,2) 添加一个新属性,3) 重写每个方法调用或依赖成员变量来代替使用该属性。这不仅是不必要的更多工作,而且如果编写派生类的人无法访问源代码,这几乎是不可能的。
如果基类使用属性而不是成员变量,那么只需在 get/set 函数中添加验证或其他行为即可,就完成了。