Generic methods

P

phancey

I'm quite new to generics. I have 2 generic classes:
MyClass<T> and MyOtherClass<T>.
MyClass<T> has 2 public Add methods
Add(MyOtherClass<T>);
Add(MyOtherClass<Wrapper<T>>); (Wrapper<T> is another class that
contains an object of type T)

I have a third generic class: MyHandler<T> which contains 2 Add
methods and a generic method:
Add(T obj)
{
Add<T>(obj);
}
Add(Wrapper<T> obj)
{
Add<Wrapper<T>>(obj);
}
Add<E>(E obj)
{
MyOtherClass<E> obj1 = new MyOtherClass<E>();
MyClass<T> obj2 = new MyClass<T>();
obj2.Add(obj1);
}

The compiler complains about obj2.Add(obj1) that "cannot convert from
MyOtherClass<E> to MyOtherClass<T>"

why doesn't this work? How should I be doing it?

very grateful for your help.
Thanks
Phil
 
P

Peter Duniho

The compiler complains about obj2.Add(obj1) that "cannot convert from
MyOtherClass<E> to MyOtherClass<T>"

why doesn't this work? How should I be doing it?

If E is not the same as T, then passing a MyOtherClass<E> in place of a
MyOtherClass<T> isn't legal. Even if E and T are related by
inheritance, it's not allowed, and I don't even see anything in your
code that suggests they are.

How you _should_ be doing it, I'm not sure. It depends on what you're
trying to do here. Why is the method generic? The code you posted
doesn't appear to do anything with the parameter anyway. So it's
unclear how you are using it.

If you expect to always be using with an object compatible with T, then
perhaps you can take advantage of that. For example, if E inherits T:

Add<E>(E obj) where E : T
{
MyOtherClass<T> obj1 = new MyOtherClass<T>();
MyClass<T> obj2 = new MyClass<T>();

/* Presumably somewhere, either in the above constructors
or later on, you use "obj" in some way related to the
T-based generic instances above. In that context,
because it's been constrained to inherit T, you can use
the "obj" parameter wherever you'd need a T */

obj2.Add(obj1);
}

It's difficult to give a nice specific answer, because the code you
posted doesn't explain why the method is the way it is. We can't really
tell what it is exactly you want to do. Another way to fix the problem
would be to use "E" instead of "T" everywhere in the method, but again,
without knowing why you wrote the method that way, it's hard to know if
that's a suitable answer.

Pete
 
P

phancey

If E is not the same as T, then passing a MyOtherClass<E> in place of a
MyOtherClass<T> isn't legal. Even if E and T are related by
inheritance, it's not allowed, and I don't even see anything in your
code that suggests they are.

How you _should_ be doing it, I'm not sure. It depends on what you're
trying to do here. Why is the method generic? The code you posted
doesn't appear to do anything with the parameter anyway. So it's
unclear how you are using it.

If you expect to always be using with an object compatible with T, then
perhaps you can take advantage of that. For example, if E inherits T:

Add<E>(E obj) where E : T
{
MyOtherClass<T> obj1 = new MyOtherClass<T>();
MyClass<T> obj2 = new MyClass<T>();

/* Presumably somewhere, either in the above constructors
or later on, you use "obj" in some way related to the
T-based generic instances above. In that context,
because it's been constrained to inherit T, you can use
the "obj" parameter wherever you'd need a T */

obj2.Add(obj1);
}

It's difficult to give a nice specific answer, because the code you
posted doesn't explain why the method is the way it is. We can't really
tell what it is exactly you want to do. Another way to fix the problem
would be to use "E" instead of "T" everywhere in the method, but again,
without knowing why you wrote the method that way, it's hard to know if
that's a suitable answer.

Pete

ok, sorry, I was trying to simplify.
ObjectHandler<T> is my DAL object handler dealing with objects that
all derive from a BaseObject so ObjectHandler<Location>,
ObjectHandler<Person> etc.
This has a choice of Add Methods that take an object as a parameter.
The object is either AddRequest<Location> etc or
AddRequest<Wrapper<Location>> etc because sometimes in my Business
Layer I add some information to the basic object by wrapping it. The
Data Access Layer works fine in NUnit tests where I either pass an
AddRequest<object> or an AddRequest<Wrapper<object>>.

My Business Layer has an ObjectManager<T> where T is also a BaseObject
so ObjectManager<Location> etc. In the Add method of this manager,
sometimes I want to pass the object untouched through to the DAL so I
will use AddRequest<Location>, sometimes I need to attach some more
info so I want to use AddRequest<Wrapper<Location>>.

So the public ObjectManager<T>.Add takes an object of type T. I then
have 2 protected internal AddObject methods, one takes an object of
type T, one takes a Wrapper<T> because internal to the Business Layer
I could be adding either kind of object. What I was trying to do then
was call a generic method AddObject<E>(E obj) where this method would
then call down to the DAL using an AddRequest<E>, so:
ObjectManager<T>.Add(T obj)
{
AddObject<T>(obj);
}
ObjectManager<Wrapper<T>>.Add(Wrapper<T> obj)
{
AddObject<Wrapper<T>>(obj);
}

Have I explained it well enough? So as far as I can tell, if
AddObject<E> is called with an object of type T, then the call to the
DAL will use an AddRequest<T> which will map to the overloaded method
that takes an AddRequest<T> type parameter. And if AddObject<E> is
called with a Wrapper<T> then it will use an AddRequest<Wrapper<T>>
which will map to that overloaded method.

Your help is greatly appreciated.

Thanks
 
P

Peter Duniho

[...]
Have I explained it well enough? So as far as I can tell, if
AddObject<E> is called with an object of type T, then the call to the
DAL will use an AddRequest<T> which will map to the overloaded method
that takes an AddRequest<T> type parameter. And if AddObject<E> is
called with a Wrapper<T> then it will use an AddRequest<Wrapper<T>>
which will map to that overloaded method.

It's late, so maybe I'm not getting it. But here's what I think I
understand:

The key here is that you don't want to write two AddObject() methods,
one that takes the T, and one that takes the Wrapper<T>. You'd prefer
to have a single AddObject() method that can take either a T or a
Wrapper<T>.

Is that right?

If so, I believe the issue is that the compiler, at the line at which
the error occurs, has no way to know that T is related to E in the way
that you know it is.

I played around with constraints a little bit. Okay, a lot. Probably
more than I should have bothered with. :) There are ways to cram the
design into generics using constraints and a shared interface between
the real class and the Wrapper<T> class, but the result really still
isn't exactly what you're looking for. I think that if generics
supported covariance between declared types, you could get it to work
more easily using constraints. But it doesn't, so you can't.

IMHO, the simplest thing is to just do overloaded versions of that
method, just as you've done elsewhere.

If you really really want to be able to use overloaded methods, I'd say
that the solution is to take a step back and rethink the overall design.
As the design exists right now, I'm not convinced there's any way to
do what you want, because of the lack of any defined relationship
between the two classes.

Now, all that said, I _am_ up way too late, and I'm hardly an expert in
generics to start with. So, maybe someone else has something more
encouraging to offer you. :)

Pete
 
P

phancey

[...]
Have I explained it well enough? So as far as I can tell, if
AddObject<E> is called with an object of type T, then the call to the
DAL will use an AddRequest<T> which will map to the overloaded method
that takes an AddRequest<T> type parameter. And if AddObject<E> is
called with a Wrapper<T> then it will use an AddRequest<Wrapper<T>>
which will map to that overloaded method.

It's late, so maybe I'm not getting it. But here's what I think I
understand:

The key here is that you don't want to write two AddObject() methods,
one that takes the T, and one that takes the Wrapper<T>. You'd prefer
to have a single AddObject() method that can take either a T or a
Wrapper<T>.

Is that right?

If so, I believe the issue is that the compiler, at the line at which
the error occurs, has no way to know that T is related to E in the way
that you know it is.

I played around with constraints a little bit. Okay, a lot. Probably
more than I should have bothered with. :) There are ways to cram the
design into generics using constraints and a shared interface between
the real class and the Wrapper<T> class, but the result really still
isn't exactly what you're looking for. I think that if generics
supported covariance between declared types, you could get it to work
more easily using constraints. But it doesn't, so you can't.

IMHO, the simplest thing is to just do overloaded versions of that
method, just as you've done elsewhere.

If you really really want to be able to use overloaded methods, I'd say
that the solution is to take a step back and rethink the overall design.
As the design exists right now, I'm not convinced there's any way to
do what you want, because of the lack of any defined relationship
between the two classes.

Now, all that said, I _am_ up way too late, and I'm hardly an expert in
generics to start with. So, maybe someone else has something more
encouraging to offer you. :)

Pete

It's not late here in the UK but I still don't get it! :blush:)

Ideally I want one method in the Business Layer that calls down to the
DAL whether I am using T or Wrapper<T>. There is an overloaded method
in the DAL that takes either AddRequest<T> or AddRequest<Wrapper<T>>
as a parameter.

So what I want to know is why the method in the Business Layer :
AddObject<E>(E obj) which then creates an AddRequest<E> cannot use
this AddRequest<E> to call down to the DAL because that AddRequest<E>
will definitely be either an AddRequest<T> or an
AddRequest<Wrapper<T>> for definite. Now that I write this more
clearly I start to understand that the compiler does not know that
another object might be sent to this method. What I need is to be able
to tell it that E is either T or Wrapper<T> but I don't think this is
possible. I suppose I will need to use overloaded methods here but I
was trying to avoid that because all of the code is actually generic
for that method on the assumption you know it is only one of these 2
types that will be sent in :blush:(

If anyone has any further advice I would appreciate it.
Phil
 
P

Paul

Are you essentially looking to have a generic access point in you DAL
for save / load?

If so generics and constraints will work.

Assuming we have an interface ISaveableBusObj
And we have I_TAccessibleWrapper - this interface will define a method
called T getmyObj<T>()

We could have

public class dataLayer
{
public bool saveMyObj<T>(T objToSave) where T : ISaveableObj
{ ... }

public bool saveMyObj<T,W>(W wrapper) where T : ISaveableObj
where W : I_TAccessibleWrapper
{ ... }
}

Does that make sense?
 
P

phancey

Are you essentially looking to have a generic access point in you DAL
for save / load?

If so generics and constraints will work.

Assuming we have an interface ISaveableBusObj
And we have I_TAccessibleWrapper - this interface will define a method
called T getmyObj<T>()

We could have

public class dataLayer
{
public bool saveMyObj<T>(T objToSave) where T : ISaveableObj
{ ... }

public bool saveMyObj<T,W>(W wrapper) where T : ISaveableObj
where W : I_TAccessibleWrapper
{ ... }

}

Does that make sense?

OK, I could look at this though it would seem to be swapping generic
classes with methods, for classes with generic methods - is there much
difference? Anyway, I think I need generic classes because there are
other DAL properties that will differ depending on what kind of object
I am dealing with. So for example I have a property SaveStoredProc
which is different depending on the type of object handler. My
ObjectHandler<T> is abstract and I have a PersonHandler derived from
ObjectHandler<Person> and a LocationHandler derived from
ObjectHandler<Location>. Each of these classes has a different
SaveStoredProc. ObjectHandler<T>.saveMyObj basically calls
SaveStoredProc with the correct parameters (simplified version of
events). Also if I switched away form generic classes now it would be
a lot of work I think...!

The object being passed to the saveMyObj is in effect a wrapper class
anyway (AddRequest) because there are other things I need like
DbTransaction etc. But the BusinessObject property of that AddRequest
will either be T or Wrapper<T> so your code would change like this:
public class dataLayer
{
public bool saveMyObj<T>(AddRequest<T> addRqs) where T :
ISaveableObj
{ ... }

public bool saveMyObj<T,W>(AddRequest<W> addRqs) where T :
ISaveableObj
where W : I_TAccessibleWrapper
{ ... }
}

is that right? So if my Business Layer had 2 methods(again the
BusinessManager is object specific much like the DAL so there is a
PersonManager derived from ObjectManager<Person>):

protected internal bool AddObject<T>(T myObj) where T : ISaveableObj
and
protected internal bool AddObject<T,W>(W myObj) where T : ISaveableObj
where W : I_TAccessibleWrapper

and each of these methods did mostly the same thing, i.e
BusinessStuff, SetupRequestObject etc so I put this into a generic
method:
private bool AddObject<E>(E myObj)
{
BusinessStuff();
AddRequest<E> addRqs = new AddRequest<E>();
addRqs.BusinessObject = myObj;
addRqs.DbTransaction = dbTrans;
ISaveableDAL dalHandler = new dalLayer(); //using your suggestion
for standard class with generic methods.
dalHandler.AddObject(addRqs);
}

this would still fail with the same problem wouldn't it? So how do I
organise the Business Layer so that I have a generic method like
AddObject<E> above that only allows either a T object or a Wrapper<T>
object?

getting very confused now but thanks for suggestions.
 
P

Paul

Ok, I think you are getting there.

The only thing you can do with generics to try and ensure what you are
getting is to add constraints.

You can also internally do some switching, for example in my code I
make heavy use of empty interface defs.

For example

public interface IAdminAccessRequired
{
}

In my code I may have

public bool saveMyObj<T>( T myBusObj, IUser authorisedUser ) where T :
ISaveable
{
// Check does this data require a User and Auth?
if( myBusObj is IAdminAccessRequired )
{
If( authorisedUser == null || authorisedUser.CanAdmin ==
false )
{
.....
}
}


}

So by using a series of constraints and Interface tests you can wield
a fair amount of code control even in a generic situation.

BUT

really, everything we are dicussing could also be done with some
carefully thought out interfaces
 
P

Peter Duniho

[...]
Now that I write this more
clearly I start to understand that the compiler does not know that
another object might be sent to this method.

I believe that is true. What I was playing with regarding constraints
was having both T and Wrapper<T> inherit a common interface, so that
they could be treated the same. But in that case, all that the compiler
knows still is that they implement the common interface. That means the
"manager" method would have to be changed to take an object with the
common interface as a parameter, rather than using the overload. That's
possible, but it introduces new complexities itself.

The basic issue here is that with generics, the compiler needs to know
what method is going to be called when it compiles the code. More
problematically, it can't defer that until some concrete use of the
generic. Everything the compiler needs to know must be known when
compiling the generic class itself (consider generic classes implemented
in a pre-compiled assembly, like those already in .NET, for example),
and generics don't provide any way for you to relate T and Wrapper<T> in
a way that makes sense at compile time.

In the error you're getting, the compiler has selected an overload
already -- it has to pick the overload at compile time -- and then it
finds that it doesn't have enough information to consistently ensure
that the parameter passed to the overload is always what it needs. Thus
the error.
What I need is to be able
to tell it that E is either T or Wrapper<T> but I don't think this is
possible. I suppose I will need to use overloaded methods here but I
was trying to avoid that because all of the code is actually generic
for that method on the assumption you know it is only one of these 2
types that will be sent in :blush:(

If anyone has any further advice I would appreciate it.

I agree with your assessment that exactly what you want isn't possible.

However, I will say that often when I find that I'm unable to do
something with generics that I'd like to do, if I take a step back and
try to figure out what it is about my underlying design that imposes the
desire to use generics in a specific way, I discover that my underlying
design is leading me down a path that's itself not a desirable one.

In a way, the barricade I run into with respect to the generic
implementation is actually a big warning flag saying that my basic
design could be improved.

Whether this is the case in your situation, I don't know. You would
have to look at a wider picture than what you've shown here, I think.
Just as an example though, do you really need to put your "value-added"
stuff in a generic "Wrapper<>" class? Or does that Wrapper<> class need
to be structured in exactly the way it is? I haven't thought about this
much, but if Wrapper<T> actually inherited T, then you could always pass
a Wrapper<T> to any method that took a T. You wouldn't need to use
overloads, but the methods would have to check the object passed in to
see whether it's a Wrapper<T> or just a plain T.

Anyway, that's just an example...if that doesn't actually work, there
are other ways to create a data structure that can be, essentially "T,
or T+other stuff" in a way that is more compatible with generics (that
is, more of the logic happens at run-time, getting around the problem
that at compile time there's not enough information to resolve ambiguities).

Pete
 
P

phancey

[...]
Now that I write this more
clearly I start to understand that the compiler does not know that
another object might be sent to thismethod.

I believe that is true. What I was playing with regarding constraints
was having both T and Wrapper<T> inherit a common interface, so that
they could be treated the same. But in that case, all that the compiler
knows still is that they implement the common interface. That means the
"manager"methodwould have to be changed to take an object with the
common interface as a parameter, rather than using the overload. That's
possible, but it introduces new complexities itself.

The basic issue here is that with generics, the compiler needs to know
whatmethodis going to be called when it compiles the code. More
problematically, it can't defer that until some concrete use of thegeneric. Everything the compiler needs to know must be known when
compiling thegenericclassitself (considergenericclasses implemented
in a pre-compiled assembly, like those already in .NET, for example),
and generics don't provide any way for you to relate T and Wrapper<T> in
a way that makes sense at compile time.

In the error you're getting, the compiler has selected an overload
already -- it has to pick the overload at compile time -- and then it
finds that it doesn't have enough information to consistently ensure
that the parameter passed to the overload is always what it needs. Thus
the error.
What I need is to be able
to tell it that E is either T or Wrapper<T> but I don't think this is
possible. I suppose I will need to use overloaded methods here but I
was trying to avoid that because all of the code is actuallygeneric
for thatmethodon the assumption you know it is only one of these 2
types that will be sent in :blush:(
If anyone has any further advice I would appreciate it.

I agree with your assessment that exactly what you want isn't possible.

However, I will say that often when I find that I'm unable to do
something with generics that I'd like to do, if I take a step back and
try to figure out what it is about my underlying design that imposes the
desire to use generics in a specific way, I discover that my underlying
design is leading me down a path that's itself not a desirable one.

In a way, the barricade I run into with respect to thegeneric
implementation is actually a big warning flag saying that my basic
design could be improved.

Whether this is the case in your situation, I don't know. You would
have to look at a wider picture than what you've shown here, I think.
Just as an example though, do you really need to put your "value-added"
stuff in ageneric"Wrapper<>"class? Or does that Wrapper<>classneed
to be structured in exactly the way it is? I haven't thought about this
much, but if Wrapper<T> actually inherited T, then you could always pass
a Wrapper<T> to anymethodthat took a T. You wouldn't need to use
overloads, but the methods would have to check the object passed in to
see whether it's a Wrapper<T> or just a plain T.

Anyway, that's just an example...if that doesn't actually work, there
are other ways to create a data structure that can be, essentially "T,
or T+other stuff" in a way that is more compatible with generics (that
is, more of the logic happens at run-time, getting around the problem
that at compile time there's not enough information to resolve ambiguities).

Pete





Good stuff to think about. I started thinking about the Wrapper<T>
inheriting from T but how could I do that? I currently have an
interface defined for Wrapper<T>:
T BusinessObject {get; set; }
object AdditionalProperties {get: set: }

(btw, I don't have access to the definitions of T, ie the Business
Objects, so cannot modify them to just have an AdditionalProperties
attribute).
Wrapper<T> implements this interface. If I could derive Wrapper<T>
from T (and I'm fairly sure this is NOT possible is it?), I couldn't
derive the interface because the Business Objects do not implement a
basic interface (i didn't write them).

thanks
Phil
 
P

Peter Duniho

Good stuff to think about. I started thinking about the Wrapper<T>
inheriting from T but how could I do that? I currently have an
interface defined for Wrapper<T>:
T BusinessObject {get; set; }
object AdditionalProperties {get: set: }

(btw, I don't have access to the definitions of T, ie the Business
Objects, so cannot modify them to just have an AdditionalProperties
attribute).
Wrapper<T> implements this interface. If I could derive Wrapper<T>
from T (and I'm fairly sure this is NOT possible is it?), I couldn't
derive the interface because the Business Objects do not implement a
basic interface (i didn't write them).

That's right, you can't literally inherit T in the declaration. But you
could redesign the code somehow so that that wasn't necessary. It's
hard for me to be specific since I don't know the specifics of your
architecture, and depending on the actual design it could be impossible.

Alternatively, a wrapper class could share the same base class as all
your other wrappable objects, and include within itself a reference to
some wrappable object. That way both kinds of objects could be passed
the same methods, which would then conditionally process the object
depending on whether it's a wrapper type or something else.

Of course, there is always the constraint-based technique mentioned by
both me and Paul. I had experimented a little with this prior to my
first reply, and found it caused more headaches than I thought it was
worth, as well as changed the fundamental design (with my reasoning
being, if you're going to change the fundamental design anyway, do so in
a simpler way than squeezing the design into generics with constraints :) ).

Pete
 

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