减少重复代码

时间:2010-03-27 00:45:57

标签: c# readability

我有一些像这样的颜色结构的代码

public void ChangeColor()
{
    thisColor.R = thisColor.R + 5;
}

现在我需要创建一个方法,根据传递的内容更改不同的变量。这是代码现在的样子。

public void ChangeColor(int RGBValue)
{
    switch(RGBValue)
    {
        case 1:
            thisColor.R = thisColor.R + 5;
            break;
        case 2:
            thiscolor.B = thisColor.B + 5;
            break;
    }
}

现在,这是我通常不会质疑的事情,我只是在它周围抛出一个#region语句并称之为一天,但这只是我所拥有的一个例子,实际的功能很长。< / p>

我希望它看起来像这样:

public void ChangeColor(int RGBValue)
{
    thiscolor.RGBValue = thiscolor.RGBValue;
}

所以基本上这个值会引用正在使用的变量。这有名字吗?这是反射的意义吗?或类似的东西......有办法做到这一点吗?

7 个答案:

答案 0 :(得分:5)

如果这是你想要的,我不能100%确定。但是在给定的例子中,听起来这可能就是你所追求的。

您可以使用ref关键字:

public void ChangeColor(ref int color)
{
    color += 5;
}

void SomeMethod()
{
    ChangeColor(ref thisColor.R); //Change the red value
    ChangeColor(ref thisColor.B); //Change the blue value
}

答案 1 :(得分:2)

这绝对是的反映。事实上,这里似乎存在许多问题。我们在这里回顾一下 - 您想要更改以下方法:

public void ChangeColor(int RGBValue)
{
    switch(...)
    {
        case ...
        case ...
        case ...
    }
}

这样的事情:

public void ChangeColor(int RGBValue)
{
    thisColor.{something-from-RGBValue} += 5;
}

问题是:

  • 方法的名称ChangeColor不会精确地描述该方法实际执行的操作。也许这是一个匿名化的工件,但不过它是这个方法的可怕名称。

  • 参数RGBValue不会准确地描述参数是什么或做什么。名称RGBValue和类型int使其听起来像实际的RGB颜色值,即浅蓝色的0x33ccff。相反,它选择设置R,G或B中的哪一个。

  • 参数只有3个有效值,但可能值的范围完全不受限制。这是bug的秘诀。更糟糕的是,单个值在方法中用作幻数。

  • 但也许最重要的是,你要求的“干净/快速方法”正是这种方法所要提供的抽象!你正在编写一种强化的方法色调,为了保持方法简短,你要求......一种强化色调的方法。这没有意义!

我只能假设您要这样做,因为您可能想要对Color执行许多不同的事情,例如:

public void Brighten(...) { ... }
public void Darken(...) { ... }
public void Desaturate(...) { ... }
public void Maximize(...) { ... }

依此类推。而且你试图避免为所有人写switch语句。

很好,但不要完全消除switch; 到目前为止是编写此代码的最有效的可读方式!更重要的是将其提取到一个 switch而不是很多,并解决上面提到的其他问题。首先,让我们从一个合理的参数类型开始,而不是int - 创建一个枚举:

public enum PrimaryColor { Red, Green, Blue };

现在,从我们想要对复合颜色的一种主要颜色执行许多操作的想法开始,所以编写通用方法:

protected void AdjustPrimaryColor(PrimaryColor pc, Func<byte, byte> adjustFunc)
{
    switch (pc)
    {
        case PrimaryColor.Red:
            internalColor.R = adjustFunc(internalColor.R);
        case PrimaryColor.Green:
            internalColor.G = adjustFunc(internalColor.G);
        default:
            Debug.Assert(pc == PrimaryColor.Blue,
                "Unexpected PrimaryColor value in AdjustPrimaryColor.");
            internalColor.B = adjustFunc(internalColor.B);
    }
}

此方法简短易读,可能永远不会改变。这是一个很好,干净的方法。现在我们可以很容易地编写各个动作方法:

public void Brighten(PrimaryColor pc)
{
    AdjustPrimaryColor(pc, v => v + 5);
}

public void Darken(PrimaryColor pc)
{
    AdjustPrimaryColor(pc, v => v + 5);
}

public void Desaturate(PrimaryColor pc)
{
    AdjustPrimaryColor(pc, v => 0);
}

public void Maximize(PrimaryColor pc)
{
    AdjustPrimaryColor(pc, v => 255);
}

(显着)优点是:

  • 枚举类型可防止调用者搞砸并传入无效的参数值。

  • 通用Adjust方法易于阅读,因此易于调试且易于维护。它也会比任何基于反射或基于字典的方法表现更好 - 这并不是说性能可能是一个问题,但我主要是说要注意它肯定不会更糟

  • 您不必编写重复的switch语句。每个单独的修饰符方法恰好是一行。

最终某处,你实际上将不得不编写一些代码,我宁愿代码是一个非常简单的switch语句而不是一塌糊涂的反思,代表,词典等等。关键是要尽可能地推广这项工作;一旦你完成了那个并创建了这个抽象,然后你就可以开始编写一个单行方法来完成“真正的”工作。

答案 2 :(得分:1)

这有点尴尬,但你可以像'参考'那样传递一个属性:

    int ThisColor { get; set; }

    public void ChangeColor(Func<int> getter, Action<int> setter)
    {
        setter(getter() + 5);
    }

    public void SomeMethod()
    {
        ChangeColor(() => ThisColor, (color) => ThisColor = color);
    }

这比反射更便宜并且它是编译时检查的(使用反射,您必须将字符串传递给GetProperty调用,并且字符串名称可能在以后的重构中与属性名称分开。)

答案 3 :(得分:0)

我倾向于使用字典而不是我怀疑最终会成为一个大型的switch语句,所以如果你创建了一个

Dictionary<int,Func<int,int>> map = new Dictionary<int, Func<int, int>>();

字典中的每个项目都可以接受输入并返回新值

所以你可以打电话给你的方法

        public int ChangeColor(int rgbValue)
    {
        return map[rgbValue](rgbValue);
    }

将执行特定于您插入的Rgb值的委托,为您分配一个委托,只需向地图添加一个新条目

map.Add(5,x => x+5);

答案 4 :(得分:0)

如果我理解正确,你想编写一个方法,它采用一些符号(或属性名称)并使用此符号定义的结构属性修改。这在C#中是不可能的(你当然可以使用反射,但是......)。

您可以使用包含代理的Dictionary来执行类似的操作来读取和写入属性的值。但是,这仍然有点冗长,因为您需要初始化字典。无论如何,代码可能如下所示:

var props = new Dictionary<string, Tuple<Func<Color, int>, Action<Color, int>>> 
  { "R", Tuple.Create(c => c.R, (c, r) => c.R = r),
    "G", Tuple.Create(c => c.G, (c, g) => c.G = g),
    "B", Tuple.Create(c => c.B, (c, b) => c.B = b) };

这将创建一个字典,其中包含字符串(属性的名称)作为键,以及包含每个属性的getter委托和setter委托的元组。现在,您的ChangeColor方法可能如下所示:

public void ChangeColor(string propName) {
  var getSet = props[propName];    
  getSet.Item2(thisColor, getSet.Item1(thisColor) + 5);
}

如果您使用自己的Get属性和Set属性而不是Tuple来使用名为Item1Item2的属性,那么代码将更具可读性。此解决方案在某些情况下可能很有用,但您仍需要在初始化字典时明确列出所有属性。

答案 5 :(得分:0)

这可能是你要找的,你可能想要添加一些错误处理。
它适用于任何公共场所的财产;并设定;方法。
如果你想有办法减少使用“魔术字符串”。

public static void ChangeProperty<T>(this object obj, string propertyName, Func<T,T> func)
{
    var pi = obj.GetType().GetProperty(propertyName);
    pi.SetValue(obj, func((T)pi.GetValue(obj, null)), null);
}
public void Change()
{
    thisColor.ChangeProperty<int>("R", (x) => x + 5);
}

答案 6 :(得分:0)

嗯,因为你给出了一个非常简单的例子,所以很难说出真正发生了什么。

但是,我真正读到的是你想要一个方法,它将根据方法的一个参数对本地状态执行一些可能的修改。

现在,操作是否相同,除了它正在做什么?

最终,您必须拥有一些可以将输入映射到所需操作的代码。可以概括多少取决于行为的相似程度(如果它总是'向属性添加5'你有更多的泛化选项......)。

您有一些选择:

  1. 编写一个封装Color结构的类。
  2. 按照Kev Hunter的建议使用Action s的查找表。
  3. 写一个开关声明。
  4. 传入一个参数,该参数包含可以对内部数据执行的虚拟方法(或者直接传入Action&lt;&gt;) - 避免查找
  5. 而且......就是这样,真的。其中哪一个最有意义可能更多地取决于您的实际用例(我们并没有真正掌握大量信息)。