How to make my methods dynamic?

T

Tino Donderwinkel

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
 
M

Marc Gravell

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
 
T

Tino Donderwinkel

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
 
T

Tino Donderwinkel

Hmm..

I could use:

return (T)(object)LoadCircle(id);
and
return (T)(object)LoadCube(id);

is that sane?

Tino
 
J

Jon Skeet [C# MVP]

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
 
M

Marc Gravell

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
 
T

Tino Donderwinkel

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
 
M

Marc Gravell

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
 
T

Tino Donderwinkel

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
 
M

Marc Gravell

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
 
T

Tino Donderwinkel

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top