Generics and casting

  • Thread starter Thread starter Alexander van Doormalen
  • Start date Start date
A

Alexander van Doormalen

I have a xml file with data from various sources. For example:

<root>
<Account>
<Id>1</Id>
<Name>Somename</Name>
<City>Somecity</City>
<ContactPersons>
<ContactPerson>
<Id>1</Id>
<Name>Somename</Name>
</ContactPerson>
<ContactPerson>
<Id>2</Id>
<Name>Somename2</Name>
</ContactPerson>
</ContactPersons>
</Account>
</root>

I created a generic businesscollection (named BusinessCollection) and a
business class (named BusinessObject). Then I created a specific
business class named Account en another one called ContactPerson. They
both derive from the BusinessObject class.

I created a instance of a businesscollection with accounts using:
BusinessCollection <Account> collection = new BusinessCollection
<Account>();

I also made a parser that 'translates' the xml into the correct
business classes. So in case of the data above I provide a instance of
BusinessCollection <Account>. Then I create Account objects and add
them to the collection. This is all done using reflection and some
XPath classes.

The parser has the folling method signature:
public static void ParseToBusinessObjects <T>(BusinessCollection <T>
collection, XPathDocument document) where T : BusinessObject

So far so good. But now I also want subcollections to parse. In the xml
data ContactPersons will be my subcollection (property is named the
same). If my parser detects a subcollection it will get the current
instance (using reflection). I then want to call my entry method again
for this collection (recursive)

However when I do this:
BusinessCollection <BusinessObject> subCollection = (BusinessCollection
<BusinessObject>) property.GetValue(instance, null);

I get a InvalidCastException saying I can't convert between a
BusinessCollection<ContactPerson> and a
BusinessCollection<BusinessObject>.

Its the same when I change it to :
BusinessCollection <T> subCollection = (BusinessCollection <T>)
property.GetValue(instance, null);


I hope someone knows how to solve this because its driving me nuts.
 
Alexander said:
I have a xml file with data from various sources. For example:

<root>
<Account>
<Id>1</Id>
<Name>Somename</Name>
<City>Somecity</City>
<ContactPersons>
<ContactPerson>
<Id>1</Id>
<Name>Somename</Name>
</ContactPerson>
<ContactPerson>
<Id>2</Id>
<Name>Somename2</Name>
</ContactPerson>
</ContactPersons>
</Account>
</root>

I created a generic businesscollection (named BusinessCollection) and
a business class (named BusinessObject). Then I created a specific
business class named Account en another one called ContactPerson. They
both derive from the BusinessObject class.

I created a instance of a businesscollection with accounts using:
BusinessCollection <Account> collection = new BusinessCollection
<Account>();

I also made a parser that 'translates' the xml into the correct
business classes. So in case of the data above I provide a instance of
BusinessCollection <Account>. Then I create Account objects and add
them to the collection. This is all done using reflection and some
XPath classes.

The parser has the folling method signature:
public static void ParseToBusinessObjects <T>(BusinessCollection <T>
collection, XPathDocument document) where T : BusinessObject

So far so good. But now I also want subcollections to parse. In the
xml data ContactPersons will be my subcollection (property is named
the same). If my parser detects a subcollection it will get the
current instance (using reflection). I then want to call my entry
method again for this collection (recursive)

However when I do this:
BusinessCollection <BusinessObject> subCollection =
(BusinessCollection <BusinessObject>) property.GetValue(instance,
null);

I get a InvalidCastException saying I can't convert between a
BusinessCollection<ContactPerson> and a
BusinessCollection<BusinessObject>.

Its the same when I change it to :
BusinessCollection <T> subCollection = (BusinessCollection <T>)
property.GetValue(instance, null);

This isn't supported. It's called covariance, and that's not supported
in .NET generics. BusinessCollection<BusinessObject> isn't a supertype
of BusinessCollection<Contact>, even though it might look like it.
That's why casts fail.

You can solve it easily though. Implement an interface. Interfaces are
a general way to do generic programming if the generic type descriptor
changes. So, on your BusinessCollecttion<T>, you implement
IBusinessCollection. That's a non-generic interface, with the same
methods.

You can now do:
IBusinessCollection subCollection =
(IBusinessCollection)property.Getvalue(instance, null);

and call your code again.

Frans


--
------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
Frans Bouma said:
This isn't supported. It's called covariance, and that's not supported
in .NET generics. BusinessCollection<BusinessObject> isn't a supertype
of BusinessCollection<Contact>, even though it might look like it.
That's why casts fail.

Just a tiny correction - it's not supported in C# generics. Apparently
..NET itself supports both covariance and contravariance, but C# doesn't
expose it. See the following link for details - it's fascinating
stuff...

http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx
 
To bad thats not possible yet :(. I hope it will be in the feature.

Since using interfaces isn't possibe because of various reasons (like
internal methods that need to be called on the object etc). So I
'solved' it by creating an internal method that expects a plain object.
I then use reflection to call the Add method on that collection (which
is passed as an object)

MethodInfo info = collection.GetType().GetMethod("Add");
object[] paramaters = new object[1];
paramaters[0] = businessObject;

info.Invoke(collection, paramaters);

Not a very clean way but it works for now.

Thnx guys for the help!
 
Alexander van Doormalen said:
To bad thats not possible yet :(. I hope it will be in the feature.

Since using interfaces isn't possibe because of various reasons (like
internal methods that need to be called on the object etc). So I
'solved' it by creating an internal method that expects a plain object.
I then use reflection to call the Add method on that collection (which
is passed as an object)

MethodInfo info = collection.GetType().GetMethod("Add");
object[] paramaters = new object[1];
paramaters[0] = businessObject;

info.Invoke(collection, paramaters);

Not a very clean way but it works for now.

Why not still use interfaces, but then cast in the code which "knows
better"? It's not great, but it's better than reflection...
 
Will look into that when I've got some time again. Thnx for the
suggestions.
 
Jon said:
Just a tiny correction - it's not supported in C# generics.
Apparently .NET itself supports both covariance and contravariance,
but C# doesn't expose it. See the following link for details - it's
fascinating stuff...

http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

Thanks, Jon :)

FB

--
------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
Alexander said:
To bad thats not possible yet :(. I hope it will be in the feature.

Since using interfaces isn't possibe because of various reasons (like
internal methods that need to be called on the object etc). So I
'solved' it by creating an internal method that expects a plain
object. I then use reflection to call the Add method on that
collection (which is passed as an object)

MethodInfo info = collection.GetType().GetMethod("Add");
object[] paramaters = new object[1];
paramaters[0] = businessObject;

info.Invoke(collection, paramaters);

Not a very clean way but it works for now.

You can also use an internal interface and implement it explicitly.
Then, cast to the interface as Jon explained. I do that too for
internal methods on my generic collections, works like a charm.

so:
internal interface IFoo
{
void MyInternalMethod();
}

public class BusinessCollection<T>: IFoo
{
void IFoo.MyInternalMethod()
{
//...
}
}


IFoo foo = (IFoo)myCollection;
foo.MyInternalMethod();

FB


--
------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
Then I need 2 interfaces as I have both internal and public methods.
Will give it some thought.
 
I added some interfaces but still the same casting problem.

Unable to cast object of type 'BusinessCollection`1[ContactPerson]' to
type 'IBusinessCollection`1[IBusinessObject]'.

OR

Unable to cast object of type 'BusinessCollection`1[ContactPerson]' to
type 'BusinessCollection`1[IBusinessObject]'.

Some details:
- public interface IBusinessCollection<T> where T: IBusinessObject
- public interface IBusinessObject

- public class BusinessCollection<T> : Collection <T>, ICollection <T>,
IBusinessCollection<T> where T: IBusinessObject
- public class User: BusinessObject, IBusinessObject
- public class ContactPerson: BusinessObject, IBusinessObject

- Property inside User class:
public BusinessCollection <ContactPerson> ContactPersons
{
get
{
return this.contactPersons;
}
}

Am I doing something completely wrong or...
 
Alexander van Doormalen said:
I added some interfaces but still the same casting problem.

Unable to cast object of type 'BusinessCollection`1[ContactPerson]' to
type 'IBusinessCollection`1[IBusinessObject]'.

OR

Unable to cast object of type 'BusinessCollection`1[ContactPerson]' to
type 'BusinessCollection`1[IBusinessObject]'.

I was talking about casting (on a single element - not the collection)
whenever you needed to use a member of the concrete class which isn't
present in the interface.
 
Ok my mistake. I though you guys ment using an interface element would
solve the inherritance problem. But you where only focussing on the
reflection call :)

Then I will just go back to passing it as an object to the method
instead of a businesscollection type. Only without the reflection
method call.

Thnx! This will do I guess
 
Back
Top