Is it possible to turn this class into a generic one?
I开发者_如何学JAVAs anyone able to "genericalize" the class listed at the bottom of this question? I would like the class not to be locked to a delegate of type "GetStateDelegate", but a general delegate. Is that possible?
I've tried a couple of things, but 1) C# does not like a class declaration of this kind:
public class StringToMethodMapper<T> where T: System.Delegate
This yields "Cannot use System.Delegate as type parameter constraint"
An hints to get me on the right track is appreciated. Also, if a solution exists, I want the consumer to be as little affected by the changes as possible. For instance, I'd rather not change the calling code for "Add(string, delegate)".
The code:
public delegate bool GetStateDelegate(object someObject);
public class StringToMethodMapper
{
private Dictionary<string, GetStateDelegate> _methods;
public StringToMethodMapper()
{
_methods = new Dictionary<string, GetStateDelegate>();
}
public void Add(string key, GetStateDelegate method)
{
_methods.Add(key, method);
}
internal virtual GetStateDelegate GetMethodFor(string key)
{
foreach (var storedKey in _methods.Keys)
{
if (key.ToUpper().StartsWith(storedKey.ToUpper()))
{
return _methods[storedKey];
}
}
return null;
}
}
In terms of making it generic... well, it's sort of possible. I have a library called Unconstrained Melody which uses IL rewriting for generics with delegate constraints - and you could use the same IL rewriter in your code. It's pretty ugly though. Basically IL supports the constraint you want, but C# doesn't. Note that there's no possible constraint for "it should be a type deriving from MulticastDelegate
but not including MulticastDelegate
itself"... so someone could create a StringToMethodMapper<MulticastDelegate>
but that's pretty unlickly.
If you're happy to stick to one kind of delegate signature (e.g. "always three parameters and a void return") then you can use the approach from George's answer. If it should be for any delegate type, then you're stuck with the IL rewriting approach or abandoning the constraint.
(Edited based on comments.)
In terms of the rest of the code, that's a very slow use of a dictionary. Currently you've got O(n) lookup. Just use normal Dictionary
access via TryGetValue
, but pass StringComparer.OrdinalIgnoreCase
(or something similar) into the constructor to get a case-insensitive match. That won't get a starts-with match, admittedly... but your current approach isn't deterministic anyway, as you can end up with "foo" and "fo" as keys in the dictionary, both of which will match - so you're relying on the order in which the iterator returns the keys. Not a great idea.
If you really need the StartsWith
behaviour, you might want to investigate implementing a trie - or if you're happy with O(N) lookup, I would keep a List<KeyValuePair<string, TDelegate>>
to make it clear that you're not really using it as a dictionary.
Something like below, perhaps (typed without an IDE). Obviously client will need to add the generic parameters, but i think the call to add still work.
public class StringToMethodMapper<T, TResult>
{
private Dictionary<string, Func<T, TResult>> _methods;
public StringToMethodMapper()
{
_methods = new Dictionary<string, Func<T, TResult>>();
}
public void Add(string key, Func<T, TResult> method)
{
_methods.Add(key, method);
}
internal virtual Func<T, TResult> GetMethodFor(string key)
{
foreach (var storedKey in _methods.Keys)
{
if (key.ToUpper().StartsWith(storedKey.ToUpper()))
{
return _methods[storedKey];
}
}
return null;
}
}
One way to do this is to swap your delegate for an interface
public interface IGetState
{
bool GetState(object someObject);
}
then you can use this interface as your constraint
public class StringToMethodMapper<T> where T: IGetState
{
...
}
You can then implement the interface to make something specific, for example:
public class FileSystemState : IGetState
{
public bool GetState(object someObject)
{
// get state from the FS
}
}
and then
var fileSystemMapper = new StringToMethodMapper<FileSystemState>();
精彩评论