使用泛型方法的扩展重构代码

时间:2011-06-13 18:50:25

标签: c# generics methods refactoring

我正在尝试重构以下代码:

public static void SaveSplitbar(RadSplitBar splitBar)
{
    Logger.InfoFormat("Saving splitBar {0}", splitBar.ID);
    RadSplitBarSetting splitbarSettings = splitBar.GetSettings();
    SerializableDictionary<string, RadSplitBarSetting> splitBarStates = GetStates<SerializableDictionary<string, RadSplitBarSetting>>();

    bool splitbarIsKnown = splitBarStates.ContainsKey(splitbarSettings.ID);

    if (splitbarIsKnown)
    {
        Logger.Debug("SplitBar is known. Overwriting data.");
        splitBarStates[splitbarSettings.ID] = splitbarSettings;
    }
    else
    {
        Logger.Debug("SplitBar is unknown. Saving data.");
        splitBarStates.Add(splitbarSettings.ID, splitbarSettings);
    }

    RadControlManager.SetStates<SerializableDictionary<string, RadSplitBarSetting>>(splitBarStates);
}

代码的逻辑在保存各种其他对象时被重用。我试图使这个代码更通用,这样我就可以有一个Save()方法而不是SaveX(),SaveY()和SaveZ()都在不同的对象上执行相同的工作。

但是,我有一点障碍。虽然传递给Save的所有对象都有“GetSettings()”方法,但是很多对象都在扩展方法中定义了这个方法:

//RadControlExtensions.cs Extension Methods
public static RadSplitBarSetting GetSettings(this RadSplitBar splitbar)
{
    RadSplitBarSetting radSplitbarSetting = new RadSplitBarSetting(splitbar.ID, splitbar.Parent.ID, splitbar.CollapseMode, splitbar.AdjacentPanesNames);
    return radSplitbarSetting;
}

因此,当我尝试重构SaveSplitbar时,我意识到我必须切换我的参数类型。当我开始走这条路时,我想,“嗯,这是愚蠢的,如果我必须确定对象的类型,我应该多次写入保存作为每个对象的扩展方法。”

虽然这是一个“好”的解决方案,但我对它不满意。保存每种控件的逻辑是相同的,我宁愿在扩展类中没有复制/粘贴逻辑。

我的下一个想法是,“好吧,如果传递给Save的任何参数保证有一个GetSettings方法 - 也许有一种方法可以让IRadControlExtensions接口暴露这个方法。我无法确定任何这样的方式每个GetSettings方法都有不同的签名 - 似乎不可能在界面中只公布一次。

这就是我所在的地方。我该如何处理这个问题呢?

编辑:另一种保存方法

public static void SavePane(CormantRadPane pane)
{
    Logger.InfoFormat("Saving pane {0}", pane.ID);
    RadPaneSetting paneSettings = pane.GetSettings();
    SerializableDictionary<string, RadPaneSetting> paneStates = GetStates<SerializableDictionary<string, RadPaneSetting>>();

    bool paneIsKnown = paneStates.ContainsKey(paneSettings.ID);

    if (paneIsKnown)
    {
        Logger.Debug("Pane is known. Overwriting data.");
        paneStates[paneSettings.ID] = paneSettings;
    }
    else
    {
        Logger.Debug("Pane is unknown. Saving data.");
        paneStates.Add(paneSettings.ID, paneSettings);
    }

    RadControlManager.SetStates<SerializableDictionary<string, RadPaneSetting>>(paneStates);
}

EDIT2:也许更好的问题是,“什么时候停止扩展一个类和时间来开始创建一个继承当前正在扩展的类的新类?”

2 个答案:

答案 0 :(得分:2)

首先,我会让每种类型都实现一个标记接口,其中包含此操作所需的属性和方法,如

 public class CormantRadPane : IWidget
 {
  ...
 } 

 public class RadSplitBar : IWidget
 {
  ...
 } 

其中IWidget看起来像

public interface IWidget
{
    int ID;
}

和每个设置类都有类似ISetting

的内容

然后你的问题来自扩展方法,应该为正确的concreate IWidget解决这个问题。要获得该类型,您只需使用。

  Type contreteWidgetType = widget.GetType(); 

记住扩展方法实际上是编译技巧。使用this关键字时,编译器会自动在扩展方法上发出ExtensionAttribute。 从Jon Skeet here的一些时髦代码中,您可以扫描程序集以获取正确的扩展方法,并调用它。

乔恩说..

  

查找使用ExtensionAttribute修饰的类,然后查找该类中也使用ExtensionAttribute修饰的方法。然后检查第一个参数的类型,看它是否与您感兴趣的类型相匹配。

    static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
        Type extendedType)
    {
        var query = from type in assembly.GetTypes()
                    where type.IsSealed && !type.IsGenericType && !type.IsNested
                    from method in type.GetMethods(BindingFlags.Static
                        | BindingFlags.Public | BindingFlags.NonPublic)
                    where method.IsDefined(typeof(ExtensionAttribute), false)
                    where method.GetParameters()[0].ParameterType == extendedType
                    select method;
        return query;
    }

希望这会有所帮助:)

答案 1 :(得分:2)

关键是你要保存的东西不是通用的......你真的不关心它的类型是什么。您只需要该实例为您提供某种设置对象(您确实关心设置的类型!)

public static void Save<TSetting>(IGetSetting<TSetting> saveMe)
  where TSetting : IHasID
{
  TSetting setting = saveMe.GetSetting();
  SerializableDictionary<string, TSetting> states =
    GetStates<SerializableDictionary<string, TSetting>>();

  bool isKnown = states.ContainsKey(setting.ID);
  if (isKnown)
  {
    states[setting.ID] = setting;
  }
  else
  {
    states.Add(setting.ID, setting);
  }
  RadControlManager.SetStates<SerializableDictionary<string, TSetting>>(states);
}


interface IGetSetting<TSetting>
{
  TSetting GetSetting();
}

interface IHasID
{
  string ID {get;}
}


class RadSplitBar : IGetSetting<RadSplitBarSetting>

class RadSplitBarSetting : IHasID

如果您没有RadSplitBar来应用IGetSetting接口,请考虑这种方式让调用者说出所需内容:

public static void Save<TSetting>(Func<TSetting> howToGetSetting)
{
  TSetting setting = howToGetSetting();
  //... rest as above.

}

调用
SaveExtensions.Save( radSplitBar.GetSettings );