2013-01-03 117 views
5

我在老化的Windows应用程序中重构代码,并且遇到了各种各样的模式,我并不确定自己喜欢:一个类具有全局颜色变量,如下所示:周围的颜色传递策略(避免参考?)

private Color myForegroundColor = Color.Azure; 
private Color myBackgroundColor = Color.Empty; 
// ...etc. 

还有一堆的这些,和他们正在被裁判各地传递给负责建立用户界面的某些部分的方法。

我收集到一个Color是一个结构体,并且每个颜色都由ref传递,以避免每次调用方法时都创建新的副本。 IE是这样的:

// Avoid creating a copy of myForgroundColor inside SetUpButton(): 
MyHelperClass.SetUpButton(ref myForegroundColor); 

我不禁感慨,这个用法ref整个类和相关类是坏的。这感觉就像一个“code smell”,虽然我真的不能指责为什么。

我见过一对夫妇类似问题的帖子,包含“使用含有颜色的类,然后将其作为一个值类型通过了”建议,但它并不完全清楚怎么会是最好的做这个。

我想什么做的是创建一个类似于下面的内容:

public class ColorContainer 
{ 
    public UiSettingsContainer() 
    { 
     MyColor = Color.Black; 
     MyNextColor = Color.Blue; 
     // ..etc... 
    } 

    public Color MyColor { get; private set; } 
    // ...etc.... 
} 

这会让我保持颜色的控制,但对记忆的影响是一点我不清楚;如果我创建了这个类的一个实例并将其传递给需要关于包含颜色的信息的方法,那么只要实现方法使用该方法就不会创建color(它是一个结构体)的副本吗?

上午我在假设该代码将创建一个新的副本,因此不太有效纠正...

// Assumption: This creates a new copy of color in memory. 
public void SetSomeColor(Color col){ 
    someComponent.color = col; 
} 

// Calling it: 
SetSomeColor(myColorContainerInstance.MyColor); 

...比这个代码,这只会利用现有的结构吗? :

// Question: Does this avoid creating a new copy of MyColor in memory? 
public void SetSomeColor(ColorContainer container){ 
    someComponent.color = container.MyColor; 
} 

// Calling it: 
SetSomeColor(myColorContainerInstance); 

我目前倾向朝着类似于以下,这是我收集的颜色在一个单独的类和重组代码位的解决方案,但继续使用ref。在这种情况下,然而,MyColor将不得不在ColorContainer公共领域,这意味着我将有超过谁可以将它设置较少的控制值:

// Assumption: This creates a new copy of color in memory. 
public void SetSomeColor(ref Color col){ 
    someComponent.color = col; 
} 

// Calling it: 
SetSomeColor(ref myColorContainerInstance.MyColor); 

这是一个很好的解决方案,还是有更好的战略处理这样的资源?

+0

你不能只是让这些用户设置一个静态/全局的家伙? –

+7

为什么你想避免复制'颜色'对象? – delnan

+0

如果'SetSomeColor'没有标记为'virtual',那么JIT就有很好的机会将方法内联,即使参数没有标记为'ref',也可以防止复制结构。 –

回答

4

这整个事情闻起来像premature optimization,部分3和具体的联系4,所以...

另一个解决方案是只删除裁判,并复制Color结构是需要的时候。结构本身并不是太大(4 byte成员和成员),除非您调用每秒变换数百万次颜色的代码,否则所需的时间和内存不是问题。

+0

感谢您的反馈,我想你是对的。当我第一次遇到这个代码时,我实际上也是这么想的。我在这里考虑这个问题的唯一真正原因是旧的/现有的代码在全世界都使用'ref'来避免重复。也许这是为了优化其他地方实际上存在更大问题的代码。想想我会选择最干净的外观,并专注于修复应用程序的其余部分。 – Kjartan

+1

使用ref将是64位(至少在64位环境中)= 8个字节。因此使用ref需要“复制”比结构本身更多,不是吗? – StampedeXV

+0

@StampedeXV不,这两个值都是8个字节。 – Rotem

1

“插槽”类比长期以来一直是我最喜欢的类型之一。每个方法参数(将分配的右手也考虑为参数)是一个插槽。每个插槽必须填充正确大小和“形状”(类型)的东西,以便调用方法(或要处理的分配)。

如果你的方法需要一个ref Color,你可以用指向存储器中Color结构的任何大小指针填充槽。当然,我不是指C风格的指针,但它仍然是同样的东西 - 它是一个数字,表示您打算使用的资源的位置,即使它没有用代码表示。在Color(不含ref)的情况下,您使用Color结构本身填充它。

根据您编译的平台,您传递的值(通过引用Color的值)可能是32位或64位。颜色结构(System.Windows.Media.Color)本身的长度仅为32位(如果使用System.Drawing.Color,则长度为64位)使其成为一个没有吸引力的命题 - 使平均情况下的情况完全相同(根据加载到堆栈上的东西的指针和大小)作为按值传递结构 - 只有在64位结构/ 32位平台配对中更好,而在32位结构/ 64位平台配对中更好。即使试图让它使用相同的实例,结构的实际值仍将被复制到其目标槽中。

现在,将类中的颜色绑定在一起(其中ref ref是默认值)会稍微改变此图片。如果你的班级含有三种颜色,你就可以获得96位(或192位)的颜色数据,并传递最多64位信息,以找到内存中该信息的正确“包装”的位置。即使在包装后,颜色仍会被复制到其目标插槽中;但现在我们增加了开销,从ldfld(加载字段)/ call(调用预先解析的方法 - 属性访问)+ ldfld/callvirt(调用运行时解析的方法 - 属性访问)+ ldfld以实际获取值。从性能角度来看,这并不能真正帮助你,除非你打算传递大量数据,然后不使用它。

长话短说 - 除非你想要实现的颜色信息的逻辑分组,不要打扰。当堆栈帧被弹出时,堆栈中的值类型会立即被清除,除非程序运行在系统总内存的〜8个字节处,否则,您真的无法使用by ref方法。颜色集合的包装类可能会使代码更清晰/更好分解,但不是更高性能。

1

通过ref传递结构通常是一件好事,除非(1)一个人想要传递值语义,或者(2)该结构很小,并且可以使用按值传递语义。

如果人们经常想要将一些变量(可能是结构体本身)作为一个组来使用,那么声明一个透明的struct类型来保存它们可能会有帮助,然后通过ref来传递它。

请注意,通过ref传递结构的成本与按值传递类引用的成本基本相同。编写一个类来保存该结构,纯粹是为了避免使用ref参数,并不容易成为性能优势。在某些情况下,它可能是有用的有型

class MutableHolder<T> 
{ public T Value; } 

然后可以申请基准语义任何结构类型,但我只建议做,如果一个需要坚持当前范围之外的参考。在ref就足够的情况下,应该使用ref