Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I make a generic optional, defaulting to a certain class?

My question is related to Is there a reasonable approach to "default" type parameters in C# Generics?, but using an inner generic class that approach doesn't work.

Given code like this:

using System;  public class FooEventArgs<T> : EventArgs {     // ... T properties and a constructor }  public class Foo<T> {     public delegate void EventHandler<FooEventArgs>(object sender, FooEventArgs<T> e);     public event EventHandler<FooEventArgs<T>> Changed } 

And with it being used like this:

public class User {     public Foo<int> foo1;     public Foo<object> foo2;      public User()     {         foo1 = new Foo<int>();         foo2 = new Foo<object>();         foo1.Changed += foo1_Changed;         foo2.Changed += foo2_Changed;     }      protected void foo1_Changed(object sender, FooEventArgs<int> e) { ... }     protected void foo2_Changed(object sender, FooEventArgs<object> e) { ... } } 

Well, I'd rather like it if I could have the generic optional, as there will be many cases where I don't know what type something will be coming in. (Data is coming from an external system which has its own variable types, which are then converted into .NET types, but I run into situations where, for example, one remote data type may turn into one of a couple of .NET types, or where it is of the "any" type—thus object would be the only real answer for that case.)

The solution which immediately occurred to me was subclassing (it was also the primary suggestion in the question linked to earlier):

public class Foo : Foo<object> {     public Foo(...) : base(...) { } }  public class FooEventArgs : FooEventArgs<object> {     public Foo(...) : base(...) { } } 

I then want to use it like this:

public class User {     public Foo foo3;      public User()     {         foo3 = new Foo();         foo3.Changed += foo3_Changed;     }      protected void foo3_Changed(object sender, FooEventArgs e) { ... } } 

The problem is that it naturally won't work with foo3_Changed accepting FooEventArgs; it needs FooEventArgs<object>, as that's what the Foo.Changed event will get pass to it (as the value will come from Foo<object>).

Foo.cs(3,1415926): error CS0123: No overload for 'foo3_Changed' matches delegate 'FooLibrary.Foo<object>.EventHandler<FooLibrary.FooEventArgs<object>>' 

Is there anything I can do about this, short of duplicating much of the class?

I did try one other thing: an implicit operator to convert from FooEventArgs<object> to FooEventArgs.

    public static implicit operator FooEventArgs(FooEventArgs<object> e)     {         return new FooEventArgs(...);     } 

This, unfortunately, doesn't seem to work, though I'm not quite clear on why:

EditBuffer.cs(13,37): error CS0553: 'FooLibrary.FooEventArgs.implicit operator FooLibrary.FooEventArgs(FooLibrary.FooEventArgs<object>)': user-defined conversions to or from a base class are not allowed 

So then, once again, is there anything I can do about this, or am I correct in thinking that it's Tough Luck and I'll just have to be content using FooEventArgs<object> (and then I guess I may as well just use Foo<object>)?

like image 887
Chris Morgan Avatar asked Dec 05 '12 06:12

Chris Morgan


People also ask

Can a non generic class have a generic method?

Yes, you can define a generic method in a non-generic class in Java.

Can a generic class be derived from another generic class?

In the same way, you can derive a generic class from another generic class that derived from a generic interface. You may be tempted to derive just any type of class from it. One of the features of generics is that you can create a class that must implement the functionality of a certain abstract class of your choice.

How do I make my generic optional?

To make a generic type optional, you have to assign the void as the default value. In the example below, even though the function takes a generic type T, still you can call this function without passing the generic type and it takes void as default.

What is the difference between a generic class and a generic method?

A generic class or structure can contain nongeneric procedures, and a nongeneric class, structure, or module can contain generic procedures. A generic procedure can use its type parameters in its normal parameter list, in its return type if it has one, and in its procedure code.


1 Answers

I don't think there's much you can do about it, to be honest. You could make Foo doubly generic:

public class Foo<TData, TArgs> where TArgs : FooEventArgs<TData> {     public delegate void EventHandler<TArgs>(object sender, TArgs e);     public event EventHandler<TArgs> Changed; } 

Then you could write:

public class Foo : Foo<object, FooEventArgs> 

... but it's really making things very complicated for very little benefit.

I would also say that even though it's a bit more verbose to include the type argument, it does make it very clear - whereas inheritance can muddy the waters in various ways. I'd steer clear of class inheritance when you're not really trying to model behaviour specialization.

The reason your implicit conversion doesn't work has nothing to do with generics, by the way - as the error message states, you can't declare a conversion (implicit or explicit) which goes up or down the inheritance hierarchy. From the C# spec section 6.4.1:

C# permits only certain user-defined conversions to be declared. In particular, it is not possible to redefine an already existing implicit or explicit conversion.

(See that section for more details.)


As a side note, I find it more common to use inheritance the other way round for generics, typically with interfaces:

public interface IFoo {     // Members which don't depend on the type parameter }  public interface IFoo<T> : IFoo {     // Members which all use T } 

That way code can receive just an IFoo without worrying about the generics side of things if they don't need to know T.

Unfortunately, that doesn't help you in your specific case.

like image 181
Jon Skeet Avatar answered Sep 30 '22 04:09

Jon Skeet