0

I am trying attempting to refactor the following code:

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

The logic of the code is reused in saving various other objects. I am trying to make this code more generic so that I will be able to have one Save() method rather than SaveX(), SaveY(), and SaveZ() all performing the same work on different objects.

I have a bit of a snag, however. While all of the objects being passed into Save have the "GetSettings()" method, many of them have this method defined in an extension method:

//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;
}

As such, when I tried to refactor SaveSplitbar, I realize that I would have to be switching on the type of my parameter. When I started going down this route I thought, "Well, this is stupid, if I have to determine the type of the object I should just write Save multiple times as extension methods for each object."

While this is an "OK" solution, I am unhappy with it. The logic behind saving each type of control is identical and I would prefer not having copy/pasted logic in my extension class.

My next thought was, "Well, if any parameter passed into Save is guaranteed to have a GetSettings method -- maybe there is a way to have an IRadControlExtensions interface which exposes this method. I was unable to determine any such way as each GetSettings method has a different signature - does not seem possible to announce it just once in the interface.

So that's where I am at. How should I be approaching this problem?

EDIT: Another Save method

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: Perhaps a better question is, "When is it time to stop extending a class and time to start creating a new class which inherits from the class currently being extended?"

Sean Anderson
  • 27,963
  • 30
  • 126
  • 237
  • 1
    does each type to be saved have a setting class, like Radsplitbar has RadSplitbarSetting? do they share a parent type? Perhaps if you post one or two of the other save methods we could help... – almog.ori Jun 13 '11 at 19:04
  • I edited in another Save method. Yes, your assumption is correct, RadSplitBar -> RadSplitBarSetting, RadPane -> RadPaneSetting, etc. The controls do not share a parent type until many levels up (to the point where I'm not comfortable associating the extension class to this parent type as it isn't descriptive for all its children). – Sean Anderson Jun 13 '11 at 19:06

2 Answers2

2

Firstly i would have each type implement a marker interface with the required properties and methods for this operation, something like

 public class CormantRadPane : IWidget
 {
  ...
 } 

 public class RadSplitBar : IWidget
 {
  ...
 } 

where IWidget looking something like

public interface IWidget
{
    int ID;
}

and each Settings class with something like ISetting

But then your problem comes in with the extention methods, which one should be resolved for the correct concreate IWidget. To get that type you could simply use.

  Type contreteWidgetType = widget.GetType(); 

Remember extention methods is actually compiler trickery. The compiler automatically emits ExtensionAttribute on extension methods when using the this keyword. Taken from some funky code from Jon Skeet here you could scan the assembly for the correct extention method, and invoke it.

Jon said..

Look for classes decorated with ExtensionAttribute, and then methods within that class which are also decorated with ExtensionAttribute. Then check the type of the first parameter to see if it matches the type you're interested in.

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

Hope this helps :)

Community
  • 1
  • 1
almog.ori
  • 7,839
  • 1
  • 35
  • 49
2

The key is not to be generic on the thing you want to save... you don't really care what its type is. You just need that instance to provide you with some sort of settings object (you do care about the type of the settings!)

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

If you don't own RadSplitBar to apply the IGetSetting interface, consider this way of getting the caller to say what is needed:

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

}

Called by

SaveExtensions.Save( radSplitBar.GetSettings );
Amy B
  • 108,202
  • 21
  • 135
  • 185