Is there any way to pass an unbound generic type to another class?

G

Guest

I want to define a class that has a generic parameter that is itself a
generic class. For example, if I have a generic IQueue<T> interface, and
class A wants to make use of a generic class that implements IQueue<T> for
all types T (so it can make use of queues of various object types
internally). As useful as this is, it doesn't seem possible. The natural (but
illegal) notation would be something like

class A<QueueClass> where QueueClass : IQueue<T>
{
a() { QueueClass<int> qInt; ... }
b() { QueueClass<byte> qByte; ... }
}

Is there any way to pass an unbound generic type to another class?

Thanks,

Mike
 
D

Dave Sexton

Hi Mike,

C# generics doesn't work that way. The compiler needs to know what T is
going to be in order for you to constrain a generic argument to a generic
Type that requires T. You can use IQueue<object> instead, for example, but
then you won't be able to use QueueClass<int>, of course.

Since you obviously need T to change after it's set anyway, its presence is
hyper-constraining your class. In other words, you should think about
making a non-generic interface such as IQueue instead since T doesn't
provide any value.
 
G

Guest

Thanks, Dave. I feared as much. Type-unsafe programming is not
(temperamentally) an option for me, so I am passing a factory with specific
methods for each of the types I am using (clumsy but liveable). This would be
a very useful feature and not to hard to make available IMO. Maybe in a
future release...

Thanks again,

Mike
 
G

Guest

Just to supplement my previous email, I should point out that contrary to the
claim below, T provides plenty of value. Once I instantiate
QueueClass<Foo> f = new QueueClass<Foo>;
QueueClass<Bar> b = new QueueClass<Bar>;

the type system statically prevents me from saying things like
Bar bar = f.pop();

I stick by my previous statement that this would be a useful and
implementable feature that doesn't actually complicate the language. I'll
think about trying to give some input to the standardization process.

Mike
 
D

Dave Sexton

Hi Mike,

Let's say, hypothetically, that your original example will compile:

class A<QueueClass> where QueueClass : IQueue<T>

How would you Type QueueClass when you want to create an instance of A?

For instance,

new A<IQueue<int>>();

wouldn't help you since you want T to be variable within the scope of a
single Type.

However,

new A<IQueue<T>>();

just doesn't make any sense. You'd be better off using:

class A<T> : IQueue<T>

but that still wouldn't accomplish what you want since you'd have to assign
T to a specific Type:

new A<int>();

What you need is a non-generic interface or a better design pattern (from
what you've wrote it sounds like you found the latter :)
 
D

Dave Sexton

Hi,

First of all, A<QueueClass> is really confusing. Let's change it to use a
standard naming convention:

class AClass<Q>

Above I'm telling the compiler that I want to define a generic "template" so
that any type may replace Q at runtime. Now, you're suggesting that Q could
be generic itself. What you really want is "unbounded" Q it seems, but in
order for the compiler to accept that it must first accept bounded Q since
being unbounded means to be "more generic", and the compiler would therefore
need to be overextended to provide such functionality. Here's your
hypothetical generic, bounded Q syntax:

class AClass<Q<T>>

Now, I've told the compiler (hypothetically) that I want AClass to compile
using Q, where Q must be a generic type that accepts generic argument T.
This would be like adding a "where" statement to the normal syntax with some
hypothetical "generic" keyword:

class AClass<Q> where Q : generic<T>

In other words, by placing <T> on the generic argument Q in the previous
example, we've added a constraint just like you could add an inheritance
constraint using the "where" statement, however some "class with one generic
argument" constraint isn't really useful at all. It's not polymorphic since
it doesn't provide a contract against which you can code AClass to use Q<T>.
It's like saying to the compiler, "give me any generic type that has one
generic argument, but I don't care about its contract". What's the point?

In your previous example you expressed a desire for something like this:

class AClass<Q> where Q : IQueue<T>

Above we want the compiler (hypothetically) to allow Q to be any IQueue<T>
implementation where T is any type. It has the same flaw as in the previous
example but with an added level of indirection. In other words, just having
any generic type isn't useful, but this example does the same thing by
allowing T to be any type even though Q must be any IQueue. It's like
saying to the compiler, "Q can be any generic type that implements
IQueue<T>, but T doesn't need to be specified when AClass is used". The
problem now is that the compiler must know that T will be supplied when
AClass is typed, otherwise you won't be able to do anything with Q inside
AClass since you don't know what the public contract is without specifying
T. Although it might seem to you that the "IQueue" in IQueue<T> is the
contract, that's not the case. IQueue<T> requires T to be defined in order
for there to be any contract - that's the point of having a generic type in
the first place.

class AClass<Q, T> where Q : IQueue<T>

The above works fine since Q and T must be defined in order to use AClass.

But want you really wanted was Q unbound, if I've understood you correctly:

class AClass<Q<>>

Well, now we're (hypothetically) telling the compiler that Q is constrained
to be any generic type at all. I'm not sure "robust" is the word I'd use ;)

I'm not sure what it is that you think the above could help you to
accomplish, but I believe that a good design pattern (most likely a simple
interface would do) should alleviate any need for generics like this.
Constraining a generic argument to be a generic type itself doesn't seem to
provide any real value, IMO.
 
D

Dave Sexton

Hi,
I think you're confusing yourself with some of the history. Let's take a
look at this from scratch.

Actually, I don't think I'm confused here at all.

I put some more thought into just to be sure :)

<snip>

(I've snipped the C++ example because I don't know C++)
Back to C#. The same as in my previous email:

class A<Q<>> where Q<T> : IQueue<T>
{
...
new Q<int>();
new Q<System.Windows.Window>();
...
}

Now if I create a generic Queue class, I can pass it to A, which uses it
to
create a variety of typed queues.

new A<MyQueue<>>()

I can see what you're trying to accomplish, but I just don't think that it's
possible for the C# compiler to work like that and I believe, as I've
already stated, there are better ways.

My understanding is that you want to be able to create multiple instances of
Q, a generic argument constrained to be a generic type itself, explicitly
typed inside the definition of class A.

To elaborate on your example, inside A:

void DoSomething()
{
Q<int> iq = new Q<int>();
Q<long> lq = new Q<long>();
}

So, in essence, you're trying to doing something like this:

class A<Q, T> where Q : IQueue<T> where T : int OR long

In other words, you're explicitly defining what T's domain should be and
expecting that the compiler will infer this from your code, whether or not
any given T is assignable to any other T. The compiler would have to locate
all of your references to Q in the class definition of A, and acknowledge
how you've typed T each time (and this is before A has any concrete
implementations - it's still just A's definition).

Once you try to create an implementation of A by typing a variable as
follows, the compiler would have to ensure that T's domain doesn't conflict
with the specified generic type:
new A<MyQueue<>>()

(note that you couldn't specify MyQueue<> since it's not concrete, it's
another generic type)

If MyQueue is defined as follows then the compiler would have to raise an
error since the typed domain of T in A's class definition conflicts with
MyQueue<T>'s constraints on its own T:

class MyQueue<T> where T : class

Or, if MyQueue has a differing number of generic arguments then the compiler
would have to raise an error as well.

All because you've defined T in A's definition before the compiler even knew
what Q, and therefore T, was going to be. So we'll assume that the compiler
will infer multiple definitions for T from the source code (T's domain).

Currently, both the compiler and the runtime, AFAIK, expect T to be typed as
a single type per implementation of A<Q>. In order for the above to work,
reflection would have to be extended somehow to allow generic reflection
into any given implementation of A so that Q can be realized as an array of
Types inferred from A's class definition. Therefore, this wouldn't just be
a compiler feature, but would require metadata for the runtime as well.

And since int and long are not assignable to one another, the compiler could
not allow the explicit use of T within A's class definition like it could
with Q.

So what you're left with, if it did actually compile, would be a generic
class (A) that uses two explicit interfaces for int and long, hard-coded as
such, while constraining Q to be any conforming generic implementation;
however, you could only use the contract defined by IQueue<T> anyway even
though T can no longer be specified by consumers. All of that and it's
still not doing anything that the following doesn't accomplish, in a clearer
manner too IMO:

class QInt32 : IQueue<int> {}
class QInt64 : IQueue<long> {}

class A<Q1, Q2>
where Q1 : IQueue, new() // note: non-generic interfaces
where Q2 : IQueue, new()
{
void DoSomething()
{
Q1 anyQ1 = new Q1();
Q2 anyQ2 = new Q2();

// here, you could work on anyQ1 and anyQ2 using the
// IQueue interface, which is the only thing that the compiler
// would allow even in your examples as well
}
}

If you want more than two generic queues to be encapsulated by a generic
class, I'd say that a better design pattern is probably in order anyway.

Have I missed anything important? If you still feel like it would be
possible (and more useful than my solution above) then please elaborate with
some examples.

<snip>
 
G

Guest

Dave,
Dave Sexton said:
All of that and it's
still not doing anything that the following doesn't accomplish, in a clearer
manner too IMO:

class QInt32 : IQueue<int> {}
class QInt64 : IQueue<long> {}

class A<Q1, Q2>
where Q1 : IQueue, new() // note: non-generic interfaces
where Q2 : IQueue, new()
{
void DoSomething()
{
Q1 anyQ1 = new Q1();
Q2 anyQ2 = new Q2();

// here, you could work on anyQ1 and anyQ2 using the
// IQueue interface, which is the only thing that the compiler
// would allow even in your examples as well
}
}

If you want more than two generic queues to be encapsulated by a generic
class, I'd say that a better design pattern is probably in order anyway.

Have I missed anything important? If you still feel like it would be
possible (and more useful than my solution above) then please elaborate with
some examples.
Trying to work around it by using A<Q1, Q2> as you suggest breaks
encapsulation. It requires all clients of A to know what Typed Queues A uses
internally. If A later wanted to use a Q<float>, all clients of A would need
to be recoded. If you think about the C++ allocator example, you can see why
clients wouldn't want to know all the possible types the Allocator<> could be
used to allocate. The pattern remains genuinely useful (and the lack of it
will probably cause me a few hundred hours of pain in designing around the
type-unsafe use of Allocator<>).

This still seems easy enough to implement to me. The interesting thing is
that the compiler has the information to generate the form above
automatically, when compiling A. The compiler would effectively create the
form A<Q1,Q2> and convert new A<MyQueue<>>() to
A<MyQueue<int>,MyQueue<long>>. If implementers of Q are allowed to put
constraints on T, they would need to be specified in the declaration of A:

class A<Q<>> where Q<T> : IQueue<T> where T : IComparable

There shouldn't be any assignment compatibility issues because everything is
correctly typed.

I would go so far as to wager that this could be done entirely within the C#
compiler, without any CLR support.
 
D

Dave Sexton

Hi,
Trying to work around it by using A<Q1, Q2> as you suggest breaks
encapsulation. It requires all clients of A to know what Typed Queues A
uses
internally. If A later wanted to use a Q<float>, all clients of A would
need
to be recoded.

That simply isn't true. IQueue (the non-generic interface) is providing the
contract for A to use, so we don't care whether Q is typed as a generic
type, and certainly don't care whether Q's parameter, T, is typed as float,
int, single, or whatever (that has been my point from the beginning). If
you wanted to consume A using float instead of long, for example, nothing in
my example of A would have to change. The only thing required is to create
a concrete implementation of the generic interface IQueue<T> (or maybe even
just IQueue):

class QSingle : IQueue<float> {}

A<QInt32, QSingle> a = new A<QInt32, QSingle>();

The difference between our examples is that you want to be able to create
any arbitrary number of Q references typed within A's class definition and
have the compiler infer from your code the number of generic arguments that
should be placed above. I've suggested that any more than two generic
collections required by one class definition is probably a bad design to
begin with, so I really don't think it deserves first-class compiler support
with generics.
If you think about the C++ allocator example, you can see why
clients wouldn't want to know all the possible types the Allocator<> could
be
used to allocate. The pattern remains genuinely useful (and the lack of it
will probably cause me a few hundred hours of pain in designing around the
type-unsafe use of Allocator<>).

Well, if Allocator is doing what I think it's doing it sounds like there's
some design flaw in the application anyway. Although, since I don't know
C++ I'm still not sure that I fully understand that example ;)
This still seems easy enough to implement to me. The interesting thing is
that the compiler has the information to generate the form above
automatically, when compiling A. The compiler would effectively create the
form A<Q1,Q2> and convert new A<MyQueue<>>() to
A<MyQueue<int>,MyQueue<long>>. If implementers of Q are allowed to put
constraints on T, they would need to be specified in the declaration of A:

class A<Q<>> where Q<T> : IQueue<T> where T : IComparable

There shouldn't be any assignment compatibility issues because everything
is
correctly typed.

I would go so far as to wager that this could be done entirely within the
C#
compiler, without any CLR support.

I guess C# could implement generics like C++ templates instead and actually
stub out the different implementations at compile-time, with all of the
extra checks that I mentioned in my last post. Stubbing would alleviate any
need for the special reflective properties that I mentioned too. However,
other compilers would have to be aware of this special metadata defining a
generic type that isn't supported by the runtime. A compiler would
therefore have to know that it must stub the implementations out itself by
analyzing the code and finding all references to A. Losing CLR support
might be the biggest flaw in the design.

I'm also still not convinced that your example above is any clearer than
mine. I actually think it's more confusing since you'll have absolutely no
idea what T might be without inspecting the full source code of A to find
T's hard-coded types. Of course, as the number of arguments increases so
does the complexity of my example, but fxcop recommends no more than 2
generic parameters anyway so any more indicates a poor design choice to me:

http://msdn2.microsoft.com/en-gb/library/ms182129(VS.80).aspx
 
L

Lucian Wischik

mps said:
I would like to have a class that's parameterized by a generic class.
This is quite useful. It would have helped me this week with my class that
wants a generic queue.

I agree that this seems useful. I don't know the answer myself, so I
had to ask for help from type-theory people. So this answer comes from
them (no doubt distorted through my own limited understanding).

What you're asking for is proper "first class" second order
polymorphism. It's found in System F. Haskell supports it so long as
you write the type signatures explicitly. So does Caml and F#. The
paper to read on this subject is

http://research.microsoft.com/~akenn/generics/TransposingFToCSharp.pdf
by Andrew Kennedy and Don Syme (who came up with .net generics in the
first place I believe). Don's now working on a .net language called
F#. He gives the following F# code which does the essence of what you
want. Obviously, therefore, it's possible to achieve what you want
without altering the CLR.

type IQueueProvider =
interface
abstract Make: unit -> 'a list
// can also write the following clarify the quantification:
// abstract Make<'a> : unit -> 'a list
end

let qnew2 = { new IQueueProvider with Make() = [] }

let testf2 (newf:IQueueProvider) =
let q1:int list = newf.Make() in
let q2:string list = newf.Make() in
()

do testf2 qnew2


As for how to achieve this in C#? My old Type-Theory supervisor from
university said that it's possible, and that I should read the paper
that I cited above to figure out how. If I have time I'll do that...
 

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