5

我在一个老化的 Windows 应用程序中重构代码,我遇到了一种模式,我不确定我是否喜欢:一个类有全局颜色变量,如下所示:

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

其中有很多,它们通过 ref 传递给负责设置 UI 某些部分的方法。

我认为 aColor是一个结构,因此每种颜色都由 ref 传递,以避免在每次调用方法时创建它的新副本。IE 类似:

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

我不禁感到ref整个班级和相关班级的这种用法很糟糕。感觉就像是一种“代码味道”,尽管我无法真正说出原因。

我看过一些关于类似问题的帖子,其中建议“使用包含颜色的类,然后将其作为值类型传递”,但目前尚不完全清楚如何最好地做到这一点。

我想做的是创建类似于以下内容的内容:

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

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

4

3 回答 3

4

整个事情闻起来像是过早的优化,特别是链接的第 3 和第 4 部分,所以...

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

于 2013-01-03T10:37:32.720 回答
1

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

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

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

现在,将颜色捆绑在一个类中(默认通过 ref 传递)会稍微改变这张图片。如果您的类包含 3 种颜色,则其中包含 96 位(或 192 位)颜色数据,传递最多 64 位信息以在内存中找到该信息的正确“包”的位置. 即使在打包之后,颜色仍然会被复制到它们的目标插槽中;但是现在我们已经增加了开销,因为必须使用ldfld(加载字段)/调用(调用预先解析的方法 - 属性访问)+ ldfld/ callvirt(调用运行时解析的方法 - 属性访问)+ ldfld 来实际获取值. 从性能的角度来看,这对您根本没有帮助,除非您打算传递大量数据然后不使用它。

长话短说 - 除非有一些你想要实现的颜色信息的逻辑分组,否则不要打扰。当堆栈帧被弹出时,堆栈上的值类型会立即被清除,所以除非您的程序运行在系统总内存不足约 8 个字节的位置,否则您实际上并没有通过 by ref 方法获得任何收益。颜色集合的包装类可能会使代码更清晰/更好,但不会提高性能。

于 2013-01-03T11:16:20.693 回答
1

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

如果一个人经常想要将一些变量(可能是结构本身)作为一个组来处理,那么声明一个透明的结构类型来保存它们可能会有所帮助,然后将其传递给ref.

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

class MutableHolder<T>
{ public T Value; }

然后可以将引用语义应用于任何结构类型,但我只建议在需要在当前范围之外保留引用时这样做。在足够的情况下ref,应该使用ref.

于 2013-01-13T00:33:44.143 回答