.NET Forum / Languages / C# / March 2008
How to make my methods dynamic?
|
|
Thread rating:  |
Tino Donderwinkel - 26 Mar 2008 10:01 GMT Hi,
Currently I have the the following methods in my class;
public Circle LoadCircle(int a) { Circle result = new Circle(); ... return result; } public Cube LoadCube(int a) { Cube result = new Cube(); ... return result; }
I want to make a Dynamic Method 'Load' in stead of the two methods I currently have. I tried stuff like this;
public T Load<T>(int a) { // here, result is determined to be either a Circle or a Cube // triedstuff like this, but that obviously doesn't work: switch (typeof(T)) { case (typeof(Circle)): break; case (typeof(Cube)): break;} return result; }
Problems I run into is that there is no conversion between types 'T' and Circle/Cube. This can be fixed by returning an 'object' and casting it on the other side. But I do not want to do that.
Furthermore, the switch statement I use won't let me use typeof(Cube) etc. in the case constructor. This can be done with multiple 'if' statements, but that's a 'dirty' solution.
Is there a way to solve this?
Tino Donderwinkel Exchange Server MVP
Marc Gravell - 26 Mar 2008 10:15 GMT In this case, yes:
public T Load<T>(int a) where T : new() { T newItem = new T(); ... return newItem; }
However, to do something useful in the "..." you might need a common base-class or interface between the different T - for example:
public static T Load<T>(int a) where T : IShape, new() { T newItem = new T(); newItem.Foo = a; return newItem; }
with:
public interface IShape { int Foo { get; set; } } public class Circle : IShape { public int Foo { get; set; } } public class Cube : IShape { public int Foo { get; set; } }
allows you to call:
Cube cube = Load<Cube>(32); Circle circle = Load<Circle>(1);
but will prevent you (at compile-time) from calling Load<int>(15) etc, as int doesn't satisfy ": IShape"
Generics doesn't offer an easy answer to non-default constructors - i.e. if you need new Cube(a) etc. There are workarounds but they aren't ideal; but post back if you need more info.
Marc
Tino Donderwinkel - 26 Mar 2008 10:46 GMT Thanks for you fast response!
In my case, the method will return structs.
E.g.
public struct Circle { public int radius; public int x; public int y; }
public struct Cube { public int l; public int x; public int y; }
Please note; this is an example. The real structs are quit complex, and aren't called Cube and Circle... They are very different. Because of this, I'm not sure if defining the interface will do me any good... Will it?
For starters, I would like to use the existing methods in the new generic method;
public T Load<T>(int a) { if (typeof(T) == typeof(Circle)) { return LoadCircle(a); } else if (typeof(T) == typof(Cube)) { return LoadCube(a); } else { throw new Exception("Invalid Type Specified."); } }
I'd prefer a switch statement, since the method should be used for about 12 structs, but I think the switch statement won't really let me do that... because of the typeof(T) etc.
I'm very new to generics. :-) never used it, besides the class List<T>... Haha.
Thanks for you help,
Tino Donderwinkel Exchange Server MVP
> In this case, yes: > [quoted text clipped - 33 lines] > > Marc Jon Skeet [C# MVP] - 26 Mar 2008 10:56 GMT <snip>
> I'd prefer a switch statement, since the method should be used for about 12 > structs, but I think the switch statement won't really let me do that... > because of the typeof(T) etc. > > I'm very new to generics. :-) never used it, besides the class List<T>... > Haha. Sounds like *really* you want a dictionary of delegates used to build the objects. You'll end up with some boxing in there unfortunately, but I can't see how that's avoidable. You'd have something like:
static readonly Dictionary<Type,Func<object>> factories = new Dictionary<Type,Func<int,object>> { { typeof(Circle), x => LoadCircle(x) }, { typeof(Cube), x => LoadCube(x) } };
public T Load<T> (int i) { Func<int,object> factory; if (!factories.TryGetValue(typeof(T), out factory)) { throw new ArgumentException("Invalid type specified"); } object ret = func(i); return (T) ret; }
At this point you don't really get much in the way of benefits from generics to be honest - the above could all be done with a normal Type parameter (except for the cast, which would have to be at the call site).
It should work though...
Jon
Tino Donderwinkel - 26 Mar 2008 11:16 GMT Thanks!
I'll try that!
Tino Donderwinkel Exchange Server MVP
> <snip> > [quoted text clipped - 36 lines] > > Jon Tino Donderwinkel - 26 Mar 2008 12:34 GMT Thanks.
It worked out.
I now have the following code;
private readonly Dictionary<Type, Func<object, object>> load = new Dictionary<Type, Func<object, object>> { {typeof(VoiceGroup), x => LoadVoiceGroup((int)x)}, {typeof(Service), x => LoadService((int)x)}, {typeof(Operator), x => LoadOperator((int)x)}, {typeof(CPEGroup), x => LoadCPEGroup((int)x)}, {typeof(User), x => LoadUser((string)x)}, {typeof(Client), x => LoadClient((string)x)} };
public T Load<T>(object identifier) where T : struct { Func<object, object> result; if (!load.TryGetValue(typeof(T), out result)) { throw new Exception("The object type '" + typeof(T).ToString() + "' is invalid."); } try { return (T)result(identifier); } catch (InvalidCastException error) { throw new Exception("The identifier type for loading an object of type '" + typeof(T).ToString() + "' is invalid."); } }
This works!
Now see if I can replace similar functions, that return a List<T> (with 'T' the types as in the code)... These take 2 parameters...
Tino
> <snip> > [quoted text clipped - 36 lines] > > Jon Marc Gravell - 26 Mar 2008 12:56 GMT Personally I'm not sure that this is going in the right direction... you've introduced boxing and taken away the ability for the caller to know what type to pass the method (and the compiler's ability to enforce it) - i.e. when loading a user do I give it an int, a string, or a bool? dunno (without looking). It also isn't clear that it *can't* load a "Foo", a "Bar" or a "Flibble".
I'm not sure you have gained much from the caller invoking LoadUser("abc") directly, rather than Load<User>("abc") (guessing the arg-type). There might be some use-cases if you are deep in the bottom of some highly generic code, but equally there may be cleaner solutions if the actual use-case is clear.
It is perhaps unfortunate (then again, perhaps not) that C# doesn't offer return-type overloading. But if the "LoadUser" etc methods (instead of "Load") offend (and I can't say that they offend me), then one final option might be using "out" to enable overloading...
public void Load(string id, out User user) {... load the user...} public void Load(int id, out VoiceGroup group) {...load the group...}
etc
Then: User user; Load(123, out user);
will pick up the correct overload at compile-time.
But I'd go for the simplest "User LoadUser(int id)" approach until there is a known reason not to...
Marc
Tino Donderwinkel - 26 Mar 2008 13:14 GMT Thank you for the warning.
I'm not sure what way to go.
In the current code I have a ton of methods. For each of the 6 structs I have, I have these methods;
Load{struct}(int/string id) Save{struct}(int/string id) Update{struct}(int/string id, int/string oldid) Get{struct}List() Get{struct}List(int start, int count) Remove{struct}(int/string id) Get{struct}Log() Get{struct}Log(int/string id) Get{struct}Log(int start, int count) Get{struct}Log(int/string id, int start, int count) Verify{struct}(int/string id) Verify{struct}(int/string id, int/string oldid)
That's 12 x 6 = 72 methods. :-(
It's just a pain to maintain these. Furthermore, it's a pain to document.
:-) Even though I use sandcastle... For testing now, I have ONE single Load function. This Load function replaces; Load{struct} Get{struct}List Get{struct}List(int start, int count) for all {struct}!
I have noticed that by doing this, I introduced other potential problems. But I'm just not sure what way to go with this... Having all these methods is a pain as well... wouldn't you say? I'm really looking for a 'best practice'... I can go both ways...
Tino
> Personally I'm not sure that this is going in the right direction... > you've introduced boxing and taken away the ability for the caller to [quoted text clipped - 29 lines] > > Marc Marc Gravell - 26 Mar 2008 13:37 GMT Well, from you "Thanks. It worked out." post (and talking about Load still), you still *have* these 6 methods... you've just added another one on top:
{typeof(VoiceGroup), x => LoadVoiceGroup((int)x)}, {typeof(Service), x => LoadService((int)x)}, {typeof(Operator), x => LoadOperator((int)x)}, {typeof(CPEGroup), x => LoadCPEGroup((int)x)}, {typeof(User), x => LoadUser((string)x)}, {typeof(Client), x => LoadClient((string)x)}
plus Load<T>
Without more info it is hard to tell how much of these 12x6 sub- methods is shared and could be sensibly refactored to share some internals. It might also be that ORM tools offer some of this for you (particularly the load/save).
But please note the original warning about using structs; when talking about graphics that tends to just about (at a stretch) be inside the small set of cases when a mutable struct makes sense. But! Things like "User", "Client", "Operator" etc - they sound 100% like they should be classes, not structs. You might have some good reason why this isn't so, but for your own sanity - make sure you understand the difference. Mutable structs are a common cause of bugs and questions.
Marc
Tino Donderwinkel - 26 Mar 2008 13:50 GMT Thanks for clarifying.
The class that holds all these methods is a 'wrapper' for a piece of code that consumes a SOAP interface to a web service. It handles the HTTP sessions, logging in and out and some more soap specific stuff. It makes 'talking' to the (not too well defined) SOAP interface more easy for the client.
The structs map to the structs defined in the WSDL, and differ quit significantly. Although a CLIENT and a USER might seem like the same thing, more or less, these are completely different entities. A client for example has multiple other required structs, and nullable values. A user is what you might expect; three strings or so in a simple struct.
I'll try to find some documentation on designing guidelines etc.
Thanks all for taking the time to help me out on this one. It's appreciated.
Tino Donderwinkel Exchange Server MVP
> Well, from you "Thanks. It worked out." post (and talking about Load > still), you still *have* these 6 methods... you've just added another [quoted text clipped - 23 lines] > > Marc Marc Gravell - 26 Mar 2008 11:21 GMT > I'm not sure if defining the interface will do me any good... Will it? It really depends on what you are doing inside the "..." - i.e. are you doing something common to them. If you aren't, then generics might not be the best approach.
Note that there is a little alarm going off in my head "mutable struct, mutable struct, ...".
Just to warn that you need to be *really* careful with these... if you are very sure that you know what you are doing, then fine - but not for the faint hearted. In most cases (especially when mutable) a class would be preferable. Can I invite you to check that you really mean this... hint: they aren't the same as C++ structs.
Marc
Tino Donderwinkel - 26 Mar 2008 10:51 GMT Hmm..
I could use:
return (T)(object)LoadCircle(id); and return (T)(object)LoadCube(id);
is that sane?
Tino
> In this case, yes: > [quoted text clipped - 33 lines] > > Marc
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|