views:

141

answers:

5

The Problem

It's something I came across a while back and was able to work around it somehow. But now it came back, feeding on my curiosity - and I'd love to have a definite answer.

Basically, I have a generic dgv BaseGridView<T> : DataGridView where T : class. Constructed types based on the BaseGridView (such as InvoiceGridView : BaseGridView<Invoice>) are later used in the application to display different business objects using the shared functionality provided by BaseGridView (like virtual mode, buttons, etc.).

It now became necessary to create a user control that references those constructed types to control some of the shared functionality (eg. filtering) from BaseGridView. I was therefore hoping to create a public property on the user control that would enable me to attach it to any BaseGridView in Designer/code: public BaseGridView<T> MyGridView { get; set; }. The trouble is, it doesn't work :-) When compiled, I get the following message:

The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)

Solutions?

I realise I could extract the shared functionality to an interface, mark BaseGridView as implementing that interface, and then refer to the created interface in my uesr control.

But I'm curious if there exists some arcane C# command/syntax that would help me achieve what I want - without polluting my solution with an interface I don't really need :-)

EDIT: For reference, I did try this innocent workaround: BaseGridView<object> MyGridView { get; set; }, and... it still isn't the answer: Cannot implicitly convert type 'InvoiceGridView' to 'BaseGridView<object>'.

Partial success (edit 2)

Ok, because covariance is only supported on interfaces, I admitted defeat and defined an interface (only showing some of it):

public interface IBaseGridView<out T> where T : class
{
    bool ScrollTo(Predicate<T> criteria);
    bool ScrollTo(T object);
}

I am now able to cast my beloved InvoiceGridView to an IBaseGridView<object> - which is awesome and I'm a happy boy again :-) However, the second ScrollTo is giving me trouble upon compilation:

Invalid variance: The type parameter 'T' must be contravariantly valid on 'GeParts.Controls.IBaseGridView.ScrollTo(T)'. 'T' is covariant.

I'm now having to modify the signature to ScrollTo(object o) - which isn't ideal but gets the job done. What suprised me was that the compiler complained about the second ScrollTo yet was happy with the first one. So it seems that one isn't allowed to pass instances of an out T, but using the type itself (eg. in Predicate<T>) is fine? Seems rather picky...

A: 

Shouldn't it be like this:

public BaseGridView MyGridView { get; set; }

public BaseGridView<T> GetMyGridView<T> { return whatever; }
public void SetMyGridView<T>( BaseGridView<T> bgv) { whatever = bgv; }

??

Edited. Matthew is right, Properties may not be generic. You would have to use a getter/setter.

EricSchaefer
this is not supported
Matthew Whited
You are right. Edited...
EricSchaefer
A: 

The following would probably work:

public BaseGridView<T> MyGridView<T> { get; set; }

The problem with your original answer is that the type parameter has to appear on the method or class declaration, not just on the return value.

Note that the compiler cannot infer generic types from return values, so you'll be required to specify T in every call to MyGridView.

JSBangs
this is not supported
Matthew Whited
Matthew is right, this doesn't work. It's true that if I added T to the class definition, it would - but would then have to create constructed types from this generic user control for every business object we have. There's better workarounds than that :-)
Dav
+1  A: 

C# doesn't support generic properties to my knowledge. Your options are either to create generic methods or to make the generic type part of your class definition.

For example:

public BaseGridView<T> GetMyGridView<T>() { ... }
public void SetMyGridView<T>(T gridView) { ... }

or

class MyClass<T> {
    public BaseGridView<T> MyGridView { get; set; }
}
Peter Ruderman
Option 1: generic methods are fine, but the question is: how do you declare the backing field? Option 2: true, but then I would need to create constructed types from MyClass<T> user control for every business object we have. There's better workarounds than that :-)
Dav
In C# 4.0, I believe you could use BaseGridView<object> for your backing field (though you certainly can't do this in prior versions). Your other option would be an ancestor of BaseGridView<>. Anyway, I think the answers to your question are (1) there is no syntax that does what you want, and (2) you should re-examine your belief that a common interface is something you don't need.
Peter Ruderman
Dav
A: 

I just tried whipping together some code and it works fine for me:

    public class A<T> where T : class
    {
        public virtual A<T> ARef
        {
            get { return default(A<T>); }
        }
    }

    public class B : A<B>
    {
        public override A<B> ARef
        {
            get
            {
                return base.ARef;
            }
        }
    }
Adam
Come to think of it, not sure if this is the same lol
Adam
Indeed, it's not the same :-) You should now create C that isn't inheriting from either A or B, has a property/field of unconstructed type A<T>, and try to access some method of A.
Dav
+1  A: 

Since you wrote

But I'm curious if there exists some arcane C# command/syntax that would help me achieve what I want

I'd like to add that C# 4.0 makes it possible to substitute derived types for a base type using < out T > for covariance. So you could do

public BaseGridView<Object> MyGridView { get; set; }

So you get a well known type but you can return whatever BaseGridView you want. The only catch is unfortunately that covariance is only allowed on interfaces! :(

hawk
Covariance is what I was looking for indeed! Read about it a while back and I knew there was something along these lines - hence my question. The unfortunate part is that it's interfaces-only for now, so all roads lead seem to lead to the same finale :-( Oh where art thou, C# 5?
Dav
" but using the type itself (eg. in Predicate<T>) is fine? " - The reason it lets you do that is because Predicate<T> itself is contravariant in T.
hawk
@hawk: ah I see... it all makes sense now, thanks for sharing the insight.
Dav