Casting Generic Classes - Possible Solution

T

Tigger

I have an object which could be compared to a DataTable/List which I am
trying to genericify.

I've spent about a day so far in refactoring and in the process gone
through some hoops and hit some dead ends.

I'm posting this to get some feedback on wether I'm going in the right
direction, and at the same time hopefully save others from going
through the process.

The starting point. I have a "Store" class which provides ways to get
hold of "Record"s...

public class Record {
}
public class Store
{
Record FindRecordById(int id)
{
// return a Record
}

public string StoreName { get {return "Store"}};
}


So my first attempt to use generics was this...

public class Record
{
}

public class Store<R> where R : Record
{
R FindRecordById(int id)
{
// return an R
}

public string StoreName { get {return "Store"}};
}


I wanted to pass Store<Record>s around and use them as a base class for
activities however I found that is not possible due to casting issues.
e.g. I can't cast Store<MyRecord> to Store<Record>.
The best explanation is that you can't cast a List<MyClass> to a
List<object> as is it will allow the caster to add objects to a MyClass
based lists!

So I tried adding an interface into the structure:

public interface IStore
{
string StoreName {get;}
}

public class Store<R> : IStore where R : Record
{
public string StoreName { get {return "Store"}};
}

I can now pass the IStore around without the casting problem.

Then I came to the issue that I wanted to add methods to the interface
based on the generic R class.
I can't make it a generic interface as this will bring me back to the
original problem.
So I came up with this structure:

public interface IStore
{
Record FindRecordById(int id);
}

public class Store<R> : IStore where R : Record
{
Record IStore.FindRecordById(int id)
{
// return a Record
}
public R FindRecordById(int id)
{
return (R)((IStore)this).FindRecordById(id);
}
}

Thus, if its cast as an IStore I have access to the non generic method,
and if its cast to the generic class I get the generic one.
They don't interfere with each other as they are visible at different
times.

Just to complete the story, I also wanted to be able to have non
generic class wrappers to my store classes but still be able to
subclass in a generic way. So I came up with this:

public class MyRecord : Record
{
}

public class MyStore<R> : Store<R> where R : MyRecord, new()
{
}

public class MyStore : MyStore<MyRecord> // non generic version
{
}

// a subclass of MyStore...

public class MyOtherRecord : MyRecord
{
}

public class MyOtherStore<R> : MyStore<R> where R : MyOtherRecord,
new()
{
}

public class MyOtherStore : MyOtherStore<MyOtherRecord>
{
}

I've just thought. With this structure I will not be able to cast a
MyOtherStore to a MyStore. This could be an issue. I'll sleep on that
one.

All this seems to be working (compiling anyhow). I still have a lot of
code to refactor.

I'm also thinking, the whole point of the exercise was to reduce
potential errors with tighter compile time type checking, but it seems
I'm ending up doing more run-time casting thus defeating the point! I
think it would still help in reducing errors in business logic which
uses these classes, which might mean its worth while.

Any comments would be appreciated.

Tigger
 
M

Marc Gravell

Try the following; when passing around the store, instead of declaring
the param as Store<Record>, you need to use (e.g. as a method)

public void SomeMethod<T>(Store<T> store) where T : Record {
// something
}

You can then call via

Store<MyRecord> x = new Store<MyRecord>(); // where MyRecord : Record
SomeMethod(x);

This works as saying "accept a store of anything that are Record
items". Inside SomeMethod, the compiler knows that any T : Record, so
you have access to any available members defined on the base-class (but
not the subclasses).

Marc
 
T

Tigger

I didn't realise you could do that, thanks!

I've decided to use interfaces as the way I pass Store references
around. It leaves me with the open option to use generics or not when I
implement the concrete classes.

I had a quick play with using generic methods and I quickly came across
my down casting issue. Once I've refactored to interfaces I''ll be able
to look into it better.

Thanks

Tigger
 
T

Tigger

Ok,

I eventually got all my code refactored to the new "generic" solution.

In summary I would say that the few benefits are outweighed by the
complexity it has introduced, and I wouldn't do it again. I might even
reverse out the generic code but I will keep the core interface
concept.

In the final solution a common template was created which I can easily
incorporate into my code generator.

The main Store...

public interface IStore // interface to use when referencing Stores
{
// as this is how all external code accesses stores the external code
// has no advantage from the fact that generics is used :-(
// all public methods/properties from the generic class have to be
duplicated here

Record NewRecord();
...
}
public sealed class Store : Store<Record>
{
// this class is needed so I can easily instantiate an instance from
// a string that represents the class to use. (new() does not seem to
help in this regard)
// sealed to stop subclassing (the generic class should be
subclassed)
}
public class Store<R> : IStore where R : Record, new()
{
Record IStore.NewRecord()
{
// I have to create an interface version of a method converting the
R into its base type
// it is only visible to the interface
return this.NewRecord();
}

public R NewRecord()
{
// actual implementation using generics
// this ends up being effectively protected as the interface
version
// is our public view of the class
return new R(); // can I do this? if not call it pseudo code
}
...
}

// example sub class...

public partial interface ICriteriaStore : IStore
{
// partial to allow code generation
// includes base store so NewRecord() is also possible!

Criteria NewCriteria();
...
}
public sealed class CriteriaStore : CriteriaStore<Criteria>{}

public partial class CriteriaStore<R> : Store<R>, ICriteriaStore where
R : Criteria, new()
{
Criteria ICriteriaStore.NewCriteria()
{
// note, no casting needed...we're actually using the generics!
return NewRecord();
}
...
}

Conclusion: Don't go there!
 

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