The base class MasterClass holds a Dictionary containing a string key and a HookObj value, where HookObj holds (from the derived class) the variable Type, and reference to it’s get/set methods.
Now when the base class MasterClass receives data from some source, it will cast/assign it like so:
//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
if (dict.ContainsKey(key))
assignVal(dict[key], value);
else
throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}
//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
try
{
if (hookobj.theType == typeof(string))
hookobj.setMethod(value);
else if (hookobj.theType == typeof(int))
hookobj.setMethod(Int32.Parse(value));
else if (hookobj.theType == typeof(float))
hookobj.setMethod(float.Parse(value));
else
throw new NotImplementedException();
}
catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
}
With HookObj being:
internal class HookObj
{
public Type theType { get; private set; }
public Action<dynamic> setMethod { get; private set; }
public Func<dynamic> getMethod { get; private set; }
public HookObj(Type theType, Action<dynamic> setMethod, Func<dynamic> getMethod)
{
this.theType = theType;
this.setMethod = setMethod;
this.getMethod = getMethod;
}
}
Use of the getter methods shouldn’t need explenation. So now let’s see how messy this becomes for the end user:
class Attachvariable : MasterClass
{
public Attachvariable(int id) : base(id) { }
public Attachvariable(int userID, string position)
: base()
{
this.userID = userID;
this.position = position;
}
int userID;
string position;
protected override void hookMethod()
{
newHookAndAdd(typeof(int), set_userID, get_userID, "userID").
newHookAndAdd(typeof(string), set_position, get_position, "position");
}
public void set_userID(dynamic theVal)
{
userID = theVal;
}
public void set_position(dynamic theVal)
{
position = theVal;
}
public dynamic get_userID()
{
return userID;
}
public dynamic get_position()
{
return position;
}
}
Where I was hoping it to be more like this:
protected override void hookMethod()
{
newHookAndAdd(ref userID, "userID");
//etc
}
But it’s not possible to store references for later use it seems.. is there any way to make this more user friendly? Creating two functions per variable is going to get really messy I’d guess.
I did something similar to @Guffa’s answer, but instead of the objects holding “Hook” wrappers for the properties, I used lambdas to get/set the properties using their original value types:
MasterClass:
HookObj
Then your subclasses of MasterClass:
You could even setup your hook registrants to provide a parser:
I didn’t go through the motions of adding the parser as an optional parameter to the base handlers, I’ll leave that to you as it’s trivial at this stage. Essentially your
assignValuewould check if theHookObjhas a parser assigned and if it does, to use it. Otherwise it goes through the same motions you already have.In fact, I would have all of the hooks use a parser and have specific
IntHookObj,FloatHookObjand so on. ThenewHookAndAddmethod would essentially be a factory. If the user provides their own parser, it would create aHookObjwith that custom parser. IfTis int/float/string it would instantiate a knownHookObjimplementation so you don’t intermix all your parsing logic with the assignment process.EDIT: I ended up throwing together an implementation using custom parsers and ditching the if/elseif/elseif type checks altogether:
Hook base classes
Standard Hook Implementations
Custom Hook Parser Handler
MasterClass:
Now, the MasterClass could use a bit of work. Specifically, I feel that the
RegisterPropertymethods (which replaced newHookAndAdd) are duplicating work and you’d have to add entries for each “standard” hook type that you support. I’m sure there’s a nicer way to do it, but for now it provides your “Attachvariable” subclasses to not care about what hooks are used, they know which types are supported natively, and its typesafe. Any types not supported natively, they must supply a typesafe parser.Attachvariable sample:
The
stringandintproperties are supported natively; they hit theRegisterPropertyoverloads tied to their type. However, thebooltypes are not supported natively so they provide their own parsing logic. The “yesno” simply checks that the string equals “yes”. The “ProperBoolean” performs the standardBoolean.Parse. Usage looks like:I thought about having the “Standard” hooks inherit from the CustomHook and just pass the parse methods down, but this way you can create new standard hooks that might have more complicated parsing logic so this should make it a bit easier to read/implement. At anyrate, I whipped this off and if I were to use it in production, I’d take more time to clean/improve/test.
A biiiiiiiig plus is this makes it very easy to unit test your individual hook type implementations. Could probably refactor the
MasterClassto make it easier to unit test (albeit it’s pretty simple now), maybe move the hook creators into a separate factory/builder.EDIT: Heck, now just throw out the
theTypefield on theHookObjbase and replace it with an interface:You can propagate the changes throughout (
HookObj<T>no longer calls a base constructor passing intypeof(T),MasterClassis now tied to anIHookObjinterface). This means you can even more easily define hooks that use whatever logic they want, parsing or otherwise, and should be even easier to test.EDIT: Yeah, here’s a sample implementation where a third party consumer of your API can provide their own hooks that can be reused across their application. If they had
Personobjects used everywhere, they just define a single hook and it can get reused:With their
PersonHookbeing:Providing the
HookObj<T>overload toRegisterPropertyalso collapsed all the overloads to something simpler with less code duplication (but still it doesn’t quite feel right yet):Results might look like: