[.net] Mess with generics [sad]

Started by
6 comments, last by DrGUI 18 years, 9 months ago
Hi again! I'm new to generics so I've probably got this all wrong :'( Firstly I'll explain that resource pools derived from ResourcePoolBase hold resources derived from ResourceItemBase. ResourceItemBase needs to hold a reference to its pool. In its constructor it takes a reference to its pool and adds itself to it (and keeps the pool) Before, I had the generic types as you'd expect: public abstract class ResourcePoolBase<itemType> : NamedDataPool<itemType> where itemType : ResourceItemBase public abstract class ResourceItemBase<> : INameable However, the ResourceItemBase needs to know the pool type to take it as a parameter and be able to add itself. Of course, adding the constraint for the pool type means that the ResourceItemBase needs to know the itemType [sad]: public abstract class ResourcePoolBase<itemType, poolType> : NamedDataPool<itemType> where itemType : ResourceItemBase<itemType, poolType> where poolType : ResourcePoolBase<itemType, poolType> public abstract class ResourceItemBase<itemType,poolType> : INameable where itemType : ResourceItemBase<itemType, poolType> where poolType : ResourcePoolBase<itemType, poolType> The error where the ResourceItemBase adds itself to the pool is: Error 157 Argument '1': cannot convert from 'DirectXEngines.ResourceItemBase<itemType,poolType>' to 'itemType' and itemType is defined as above - the very same. No conversion is needed! But I think I messed it up with the cyclic restrictions :( Any solutions...really hard so major rates!
Advertisement
Looks like a cyclic dependency...

You can solve this by creating an interface IPoolData
And then implement IPoolData by the ResourcePoolBase class and refer to IPoolData in:

public abstract class ResourceItemBase<itemType,poolType> : INameable
where itemType : ResourceItemBase<itemType, poolType>
where poolType : IPoolData

Cheers
Thanks ernow - that would work if I only needed to add and remove using the pool, but I need to use the pool in other places too - I used to cast up like so:

in ResourceItemBase
/// <summary>
/// Gets the pool that the <see cref="ResourceItemBase"/> is kept in.</summary>
public ResourcePoolBase ResourcePool {
get {return m_ResourcePool;}
}

in Texture
/// <summary>
/// Gets the pool that the <see cref="Texture"/> is kept in.</summary>
public TexturePool TexturePool {
//WILL ALWAYS SUCCEED BECAUSE TYPE ENFORCED IN CONSTRUCTOR
get {return base.ResourcePool as TexturePool;}
}

but I'm sure the cast from ResourcePoolBase<ResourceItemBase> to TexturePool<Texture> failed even though the latter pool and its type are both derived!
Right, I've changed it back now...that assembly compiles and it's all lovely.

However, it isn't going to let me win that easily. Trouble when deriving :(

Here's one of the base constructors:
protected ResourceItemBase(ResourcePoolBase<ResourceItemBase> resourcePool, string name){	if (resourcePool == null) throw new System.ArgumentNullException("resourcePool");	m_ResourcePool = resourcePool;	m_Name = name;	//Add this resource to the resource pool	m_ResourcePool.Add(this);}


and here's a constructor from the derived class:
protected VideoResourceItemBase(VideoResourcePoolBase<VideoResourceItemBase> videoResourcePool, string name): base(videoResourcePool, name){}


and on that last piece of code...

Argument '1': cannot convert from 'DirectXEngines.D3DGraphicsEngine.VideoResourcePoolBase<DirectXEngines.D3DGraphicsEngine.VideoResourceItemBase>' to 'DirectXEngines.ResourcePoolBase<DirectXEngines.ResourceItemBase>'

It won't even let me downcast :( What's going on?! [sad]
Don't take it bad, but it really looks like a mess!
I don't understand why you need to make items generic in the first place ? You said: "the ResourceItemBase needs to know the pool type to take it as a parameter and be able to add itself".

I don't think so. What about this:
abstract class ResourcePoolBase<itemType> : NamedDataPool<itemType> where itemType : ResourceItemBase{}abstract class ResourceItemBase : INameable{   public void Add(ResourcePoolBase<ResourceItemBase> pool)   { pool.Add(this); }}


Doesn't this work ?

And I don't understand what's so bad about doing it the other way round:
abstract class ResourcePoolBase<itemType> : NamedDataPool<itemType> where itemType : ResourceItemBase{   public void Add(itemType item)   { /* blahblah */ }}abstract class ResourceItemBase : INameable{ }// And later in code, instead of calling: item.Add(pool), call:pool.Add(item);


Which looks more logical to me.

Regards,
jods
Quote:Original post by jods
Don't take it bad, but it really looks like a mess!

I know jods!
Quote:
I don't understand why you need to make items generic in the first place ? You said: "the ResourceItemBase needs to know the pool type to take it as a parameter and be able to add itself".

I don't think so. What about this:
*** Source Snippet Removed ***

Doesn't this work ?

Yea, I had it as in your lovely code snippet before I got into the mess with generics.

Erm...I don't want to seem rude but I never put item.Add(pool); I had 'm_ResourcePool.Add(this);'


Right...sorry, I should probably have explained why I got into the mess better.
I now have ResourceItemBase and ResourcePoolBase as before the mess, and in the forms you put in your code snippet. That compiles without errors, but the trouble is when deriving.
I derive from ResourcePoolBase VideoResourcePoolBase. As you might expect, I want this to contain VideoResourceItemBase rather than ResourceItemBase.<br><br>Here is a sample constructor from each:<br><!–STARTSCRIPT–><!–source lang="c#"–><div class="source"><pre><br><span class="cpp-keyword">protected</span> ResourceItemBase(ResourcePoolBase&lt;ResourceItemBase&gt; resourcePool, <span class="cpp-keyword">string</span> name)<br>{<br> <span class="cpp-keyword">if</span> (resourcePool == <span class="cpp-literal">null</span>) <span class="cpp-keyword">throw</span> <span class="vb-function">new</span> System.Argument<span class="cpp-literal">Null</span>Exception(<span class="cpp-literal">"resourcePool"</span>);<br><br> m_ResourcePool = resourcePool;<br> m_Name = name;<br> <span class="cpp-comment">//Add this resource to the resource pool</span><br> m_ResourcePool.Add(<span class="cpp-keyword">this</span>);<br>}<br><br><span class="cpp-keyword">protected</span> VideoResourceItemBase(VideoResourcePoolBase&lt;VideoResourceItemBase&gt; videoResourcePool, <span class="cpp-keyword">string</span> name) : <span class="cpp-keyword">base</span>(videoResourcePool, name)<br>{<br>}<br><br><br><br><br><br><br></pre></div><!–ENDSCRIPT–><br>The problem is that VideoResourcePoolBase&lt;VideoResourceItemBase&gt; will simply not cast down to ResourcePoolBase&lt;ResourceItemBase&gt;. I can see why casting down the &lt;VideoResourceItemBase&gt; bit might be disallowed (as the VideoResourcePool wouldn't have type safety any more) but as I'm downcasting both together I think it should be fine.<br><br>Actually I just thought that I might be able to specify the type of the item as a generic parameter to the ResourceItemBase, rather than the pool's type (because I can define in the item ResourceItemBase&lt;T&gt;, the T would be it's derived type) as in the mess since I'm allowed to cast down the pool type…<br>I don't know if I'll have time to try that before I go away for 3 or 4 days..I have to go like right now…<br>But this might work!!<br><br>Thanks a lot :)<br><br><br><br><br>EDIT:<br><br>Right, I've tried my idea and now I have:<br>public abstract class ResourcePoolBase&lt;T&gt; : NamedDataPool&lt;T&gt; where T : ResourceItemBase&lt;T&gt;<br>and<br>public abstract class ResourceItemBase&lt;T&gt; : INameable where T : ResourceItemBase&lt;T&gt;<br><br>m_ResourcePool is defined as:<br>ResourcePoolBase&lt;T&gt; m_ResourcePool;<br><br>and the sample constructor:<br>protected ResourceItemBase(ResourcePoolBase&lt;T&gt; resourcePool, string name)<br>{<br>//removed other irrelevant stuff<br> //Add this resource to the resource pool<br> m_ResourcePool.Add(this); //Add takes 'T item' as an argument<br>}<br><br>I get the error 'best overloaded match for … has some invalid arguments:<br>Argument '1': cannot convert from 'DirectXEngines.ResourceItemBase&lt;T&gt;' to 'T''<br><br>What is T? T is constrained to ResourceItemBase&lt;T&gt;.<br><br>So basically that error is saying 'cannot convert from 'DirectXEngines.ResourceItemBase&lt;T&gt;' to 'DirectXEngines.ResourceItemBase&lt;T&gt;'<br><br>EDIT 2: Also, note that the two Ts are convertible (I didn’t get that compiler error saying that T must be convertible) because they both have the same constraints.<br><br>I think I need to inform Microsoft, as I'm sure &#111;ne should be able to do such a thing…<br><br><!–EDIT–><span class=editedby><!–/EDIT–>[Edited by - DrGUI on July 21, 2005 4:58:21 PM]<!–EDIT–></span><!–/EDIT–>
wait a second... In your EDIT example, isn't that a recursive definition ?

public abstract class ResourceItemBase<T> : INameable where T : ResourceItemBase<T>

I can't see any sensible way for this to work...

Now, in your original example, I'm surprised that your VideoResourcePoolBase is generic (?!). What is its definition exactly ? I was expecting this:

class VideoResourcePoolBase : ResourcePoolBase<VideoResourceItemBase>
{}

which is not generic, and should work.

Good luck,
jods
Thanks again...but I need VideoResourcePoolBase to be generic because I make it non-generic when I derive the specific resource pools like TexturePool : VideoResourcePoolBase<Texture> is what I need.

I got a quick response from the .NET team - haven't had time to read yet but I'm going to be kicked off the internet in a min so I'll just paste it here:


Quote:
I can’t say I fully understand what you’re trying to do, but I think solving your problem may require some amount of refactoring of your code, or use of covariance or contravariance of generic type parameters. From your web site description, this was the interesting part:



The problem is that VideoResourcePoolBase<VideoResourceItemBase> will simply not cast down to ResourcePoolBase<ResourceItemBase>. I can see why casting down the <VideoResourceItemBase> bit might be disallowed (as the VideoResourcePool wouldn't have type safety any more) but as I'm downcasting both together I think it should be fine.



This is not allowed, normally. Assuming you have A and B as a subclass of A, you can assign a Derived to a Base, but not Derived to a Derived<A>, let alone a Base<A>. However, with variance, there may be a way to solve some of your problems.



Note that the restrictions on where you can place covariant and contravariant type parameters within an interface make it highly unlikely we’ll ever see a collection that is both readable and writable that uses variance in any significant way. So your problem may not be solvable, even with variance. You may be best off doing something like subclassing CollectionBase<T>, perhaps instantiating something like ResourcePool<Base> and ResourcePool<Derived>, and keeping them as separate types.



But with that being said, here’s some info on variance. We’ve added in some syntax to IL for variance annotations. +T means T is co-variant and –T means T is contra-variant. An unmarked T means non-variant. In terms of assignment rules, here they are:



· For a type G<+T>: values of type G<C> can be cast to values of type G<D> if C can be cast to D (for reference types C and D)

· For a type G<-T>: values of type G<C> can be cast to values of type G<D> if D can be cast to C (for reference types C and D)

· For a type G<T>: values of type G<C> can be cast to values of type G<D> only if C=D (existing rule)



To ensure interfaces with covariant or contravariant type parameters are safe, we have restrictions on where type parameters can occur in method signatures:



· Co-variant type parameters can only appear in “positive” (think “producer” or “reader” or “getter”) positions in the type definition, which roughly speaking means:

o result types of methods

o inherited interfaces

· Contra-variant type parameters can only appear “negative” (think “consumer” or “writer” or “setter”) positions in the type definition, which roughly speaking means:

o argument types of methods

· Non-variant type parameters can appear anywhere.



This does take a while to wrap your head around. I’ve got an example that I used to help get familiar with the idea. I also wrote a bunch of types that were attempting to break the rules above, and fortunately, the strict definition of where covariant and contravariant type parameters in signatures can occur mean that most of what you can express is safe, and even in the types where you try to make the same type parameter simultaneously covariant & contravariant, you still need a cast somewhere, which can fail and ensures you have type safety somewhere in your class.





Look at GenTypes.cs and Assign.cs. Note that C# doesn’t support variance annotations, so bld.bat will compile GenTypes.cs, disassemble it, make a few type parameters on interfaces covariant or contravariant, & reassemble GenTypes.dll. Then in Assign.cs, I’ve had to cast some types to Object then back down to the appropriate types to work around the lack of understanding of variance in C#.



Also, in post-beta 2 builds of the CLR, I’ve improved the error messages for InvalidCastExceptions to ensure we include the entire generics instantiation, which is really useful for debugging covariance & contravariance issues (as well as looking at T[], which you can assign to IList&ltU> if T[] is assignable to U[]).



If you build anything particularly compelling here, let us know. We’d be interested in seeing what you can come up with. But I suspect you’ll discover you’re better off subclassing CollectionBase<T>, probably because you want to be able to both add & retrieve items from your pool.


[Edited by - DrGUI on July 22, 2005 12:01:17 PM]

This topic is closed to new replies.

Advertisement