Generics Help

B

Bryan Kyle

Hi All,

I'm fairly new to C# and Generics and I'm wondering if anyone has some
suggestions for me.

I'm trying to implement a simple DAO framework using generics to keep
my code as clean as I can, however I'm getting an error with what seems
to me to be correct code. The error I'm getting is:

Error 1 Cannot implicitly convert type 'Sample.PersonDao' to
'Sample.Dao<Sample.Model>' F:\Documents and Settings\Bryan\My
Documents\Visual Studio
2005\Projects\EventManager\EventManager\Sample.cs 22 24 EventManager

Below is a snippet of code that will reproduce compilation error I
receive.

namespace Sample
{
// Base class for all Model objects.
abstract class Model { }

// Base class for all Data Access Objects.
abstract class Dao<TModel> where TModel : Model { }

// Person model.
class Person : Model { }

// Data Access Object for a Person model.
class PersonDao : Dao<Person> { }

// Factory that returns the DAO for a model.
class DaoFactory
{
public Dao<Model> GetDao(Model model)
{
if (model is Person)
{
// Compilation error on the next line.
return new PersonDao();
}

return null;

}
}
}

Am I doing something completely wrong here? Is there something I
should be doing? Is there an alternate approach that might work
better?

Thanks in advance,
Bryan Kyle
 
T

Tom Spink

Bryan said:
Hi All,

I'm fairly new to C# and Generics and I'm wondering if anyone has some
suggestions for me.

I'm trying to implement a simple DAO framework using generics to keep
my code as clean as I can, however I'm getting an error with what seems
to me to be correct code. The error I'm getting is:

Error 1 Cannot implicitly convert type 'Sample.PersonDao' to
'Sample.Dao<Sample.Model>' F:\Documents and Settings\Bryan\My
Documents\Visual Studio
2005\Projects\EventManager\EventManager\Sample.cs 22 24 EventManager

Below is a snippet of code that will reproduce compilation error I
receive.

namespace Sample
{
// Base class for all Model objects.
abstract class Model { }

// Base class for all Data Access Objects.
abstract class Dao<TModel> where TModel : Model { }

// Person model.
class Person : Model { }

// Data Access Object for a Person model.
class PersonDao : Dao<Person> { }

// Factory that returns the DAO for a model.
class DaoFactory
{
public Dao<Model> GetDao(Model model)
{
if (model is Person)
{
// Compilation error on the next line.
return new PersonDao();
}

return null;

}
}
}

Am I doing something completely wrong here? Is there something I
should be doing? Is there an alternate approach that might work
better?

Thanks in advance,
Bryan Kyle

Hi Bryan,

Unfortunately, this version of C# does not support
contravariance/covariance, which is what you're trying to do.

For example, List<A> is not the same, and is not a subset of List<B> even if
A inherits from B.
 
D

David Browne

Tom Spink said:
Hi Bryan,

Unfortunately, this version of C# does not support
contravariance/covariance, which is what you're trying to do.

For example, List<A> is not the same, and is not a subset of List<B> even
if
A inherits from B.

Actually this will work, it's just hard to convince the compiler that it
will work. In the sample below I made two small changes.

First I introduced a non-generic superclass Dao over Dao<TModel>. Without
this Dao<TModel> is a family of completely unrelated types, each the root of
its own inheritence hierarchy.

Second I declared the method GetDao as a generic method, and introduced an
obfuscating (object) cast to supress the compiler warning and changed the
type comparison from "is" to "==" because "is" includes subclasses, and this
isn't what you want:


public static Dao<TModel> GetDao<TModel>() where TModel : Model
{
if (typeof(TModel) == typeof(Person))
{
// Compilation error on the next line.
return (Dao<TModel>)(object)new PersonDao();
}


Complete sample:

namespace Sample
{
// Base class for all Model objects.
abstract class Model { }


// Base class for all Data Access Objects.
abstract class Dao { }

// Family of base classes.
abstract class Dao<TModel> : Dao where TModel : Model
{

}

// Person model.
class Person : Model { }

// Data Access Object for a Person model.
class PersonDao : Dao<Person>
{


}

// Factory that returns the DAO for a model.
class DaoFactory
{
public static Dao<TModel> GetDao<TModel>() where TModel : Model
{
if (typeof(TModel) == typeof(Person))
{
// Compilation error on the next line.
return (Dao<TModel>)(object)new PersonDao();
}

return null;

}
}
}


David
 
B

Barry Kelly

Bryan Kyle said:
I'm trying to implement a simple DAO framework using generics to keep
my code as clean as I can, however I'm getting an error with what seems
to me to be correct code.

The thing you are running into is called generic covariance, which C#
doesn't support. Basically, a List<Pigeon> is not a List<Animal>. Why?
Well, if it were true, you'd be able to do this:

List<Animal> animals = new List<Pigeon>(pigeons);
animals.Add(new Cat());

.... and that would really throw the cat amongst the pigeons, since it
would break type safety.

(The analogue in your example is that Dao<Person> is not a Dao<Model>.)

The solution to your problem is somewhat complex, and I've gone into
detailed explanations of similar situations on this newsgroup in the
past - search for generics covariance, about 2-6 weeks ago, you should
find it.

-- Barry
 
B

Barry Kelly

David Browne said:
Actually this will work, it's just hard to convince the compiler that it
will work.

There are always workarounds for genuinely type-safe scenarios - but
your modifications don't amount to a contradiction, since your code
doesn't enable covariance :)

-- Barry
 
G

Guest

Look for the "Liskov substitution principle"

I also saw a great explanation of this on www.dnrtv.com show number 0009,
where Venkat Subramaniam explained this in detail.

Cheers,
Rick
 
B

Bryan Kyle

Thanks David!

Your suggestions helped me quite a bit. I've taken your sample code
and added a driver for it to see how it works and it works great!

Below is the sample working code + driver in case anyone else runs
across a similar issue.

using System;

namespace Sample
{
// Base class for all Model objects.
abstract class Model { }

abstract class Dao { }

// Base class for all Data Access Objects.
abstract class Dao<TModel> : Dao where TModel : Model
{
public abstract TModel Get();
}

// Person model.
class Person : Model { }

// Data Access Object for a Person model.
class PersonDao : Dao<Person>
{
public override Person Get()
{
return new Person();
}
}

// Thing model.
class Thing : Model { }

// Data Access Object for a Thing model.
class ThingDao : Dao<Thing>
{
public override Thing Get()
{
return new Thing();
}
}

// Factory that returns the DAO for a model.
class DaoFactory
{
public Dao<TModel> GetDao<TModel>() where TModel : Model
{
Dao dao = null;
if (typeof(TModel) == typeof(Person))
{
dao = new PersonDao();
}
else if (typeof(TModel) == typeof(Thing))
{
dao = new ThingDao();
}
return (Dao<TModel>)dao;
}
}

static class Driver
{
static void Main()
{
DaoFactory factory = new DaoFactory();

Dao<Person> personDao = factory.GetDao<Person>();
Console.WriteLine("DAO is " + personDao);
Person p = personDao.Get();
Console.WriteLine("Model is " + p);


Dao<Thing> thingDao = factory.GetDao<Thing>();
Console.WriteLine("DAO is " + thingDao);
Thing t = thingDao.Get();
Console.WriteLine("Model is " + t);

}

}
}
 
T

Tom Spink

David said:
Actually this will work, it's just hard to convince the compiler that it
will work. In the sample below I made two small changes.

First I introduced a non-generic superclass Dao over Dao<TModel>. Without
this Dao<TModel> is a family of completely unrelated types, each the root
of its own inheritence hierarchy.

Second I declared the method GetDao as a generic method, and introduced an
obfuscating (object) cast to supress the compiler warning and changed the
type comparison from "is" to "==" because "is" includes subclasses, and
this isn't what you want:


public static Dao<TModel> GetDao<TModel>() where TModel : Model
{
if (typeof(TModel) == typeof(Person))
{
// Compilation error on the next line.
return (Dao<TModel>)(object)new PersonDao();
}


Complete sample:

namespace Sample
{
// Base class for all Model objects.
abstract class Model { }


// Base class for all Data Access Objects.
abstract class Dao { }

// Family of base classes.
abstract class Dao<TModel> : Dao where TModel : Model
{

}

// Person model.
class Person : Model { }

// Data Access Object for a Person model.
class PersonDao : Dao<Person>
{


}

// Factory that returns the DAO for a model.
class DaoFactory
{
public static Dao<TModel> GetDao<TModel>() where TModel : Model
{
if (typeof(TModel) == typeof(Person))
{
// Compilation error on the next line.
return (Dao<TModel>)(object)new PersonDao();
}

return null;

}
}
}


David

Hi David,

That's not contravariance.
 
J

Joanna Carter [TeamB]

"Bryan Kyle" <[email protected]> a écrit dans le message de (e-mail address removed)...

| Below is the sample working code + driver in case anyone else runs
| across a similar issue.

Iµ'm sorry, but I really have to ask why on earth you are doing the extra
step of inheriting from a generic class just to get classes that are
differently named ??

| // Data Access Object for a Person model.
| class PersonDao : Dao<Person>
| {
| public override Person Get()
| {
| return new Person();
| }
| }

This is totally unnecessary. What is wrong with just using Dao<Person > or
Dao<Thing> ?

Then you don't even need your factory :

public class Dao<modelT>
{
private modelT model;

public Dao(modelT model)
{
this.model = model;
}

public modelT Model
{
get { return model; }
}
}

{
Person p = new Person();

Dao dao = new Dao<Person>(p);

Model model = dao.Get();

// or you could assign it straight itno a Person, if you could be sure of
the type...

if (dao is Dao<Person>)
Person person = (Person) dao.Model

...
}

Or am I missing something ?

Joanna
 
D

David Browne

Joanna Carter said:
"Bryan Kyle" <[email protected]> a écrit dans le message de (e-mail address removed)...

| Below is the sample working code + driver in case anyone else runs
| across a similar issue.

Iµ'm sorry, but I really have to ask why on earth you are doing the extra
step of inheriting from a generic class just to get classes that are
differently named ??

| // Data Access Object for a Person model.
| class PersonDao : Dao<Person>
| {
| public override Person Get()
| {
| return new Person();
| }
| }

This is totally unnecessary. What is wrong with just using Dao<Person > or
Dao<Thing> ?

Presumably because PersonDao has additional implementation logic particular
to Person. Typically this would be somethind like key access or lookup
methods. EG:

public Person Lookup(string FirstName, string LastName)

This obviously can't go in the definition of Dao<TModel> since it applies
only for a particular TModel type.

David
 
D

David Browne

Tom Spink said:
....


Hi David,

That's not contravariance.


No indeed. But contravariance/covariance of generic parameters wasn't the
goal, and it wasn't really the problem with the OP's code.

The problem was that the return type of the GetDao method was just wrong.

public Dao<Model> GetDao(Model model)

Even if it had compiled, it's broken since a Dao<Model> is pretty useless,
and would require a client-side downcast to be at all useful.

public Dao<TModel> GetDao<TModel>() where TModel : Model

Avoids the problems by returning the type that the client actually wants to
use.

David
 
B

Bryan Kyle

Joanna said:
"Bryan Kyle" <[email protected]> a écrit dans le message de (e-mail address removed)...

| Below is the sample working code + driver in case anyone else runs
| across a similar issue.

Iµ'm sorry, but I really have to ask why on earth you are doing the extra
step of inheriting from a generic class just to get classes that are
differently named ??

The reason I'm wanting to use generics for this is so that I can define
a parameterized interface for the ModelDAO objects. For example, each
ModelDAO needs to have a Get method that is passed a type of Model and
will return an instance of the same type.

e.g.

public class PersonDAO
{
public Person Get(Person person);
}

public class ThingDAO
{
public Thing Get(Thing thing);
}

Had I not used generics for this, then implementing these methods would
be either left up to a development policy, or written generically:

public class PersonDAO
{
public Model Get(Model model);
}

While there's nothing wrong with that approach, it doesn't seem as
clean and again it is left up to the developer and the runtime
environment to determine if a given Model can be persisted using a
given ModelDAO.
 

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