Is generic constructor in non-generic class supported?
Asked Answered
N

3

64

Is it not supported, is it supported but I have to do some tricks?

Example:

class Foo
{
  public Foo<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

the generics are only used in constructor, there is no field/property depended on them, I use it (generics) to enforce the type correlation for f1 and f2.

Remark: I found the workaround -- static method Create, but anyway I am curious why I have problem with straightforward approach.

Nimrod answered 31/8, 2010 at 7:35 Comment(0)
K
93

No, generic constructors aren't supported in either generic or non-generic classes. Likewise generic events, properties and finalizers aren't supported.

Just occasionally I agree it would be handy - but the syntax would look pretty awful. For example, suppose you had:

public class Foo<T> {}

public class Foo
{
    public Foo<T>() {}
}

What would

new Foo<string>()

do? Call the generic constructor of the non-generic class, or the normal constructor of the generic class? You'd have to differentiate between them somehow, and it would be messy :(

Likewise, consider a generic constructor in a generic class:

public class Foo<TClass>
{
    public Foo<TConstructor>() {}
}

How would you call the constructor? Hopefully we can all agree that:

new Foo<string><int>()

is pretty hideous...

So yes, semantically it would be occasionally useful - but the resulting ugliness counterbalances that, unfortunately.

Kablesh answered 31/8, 2010 at 7:36 Comment(6)
You could get around the same-named class problem by not allowing a generic and non-generic class with the same name (seriously, does C# allow this?). For the generic constructor of the generic class, I don't think it's too hideous -- it's just higher order genericity.Merge
One remark -- constructor in generic class is generic, because the class is generic. However (taking your answer) it cannot specify extra generic arguments. Thank you for very good examples!Nimrod
@Peter: No, the samed-named class problem isn't a problem, because although you can "overload" classes by type arity, there's no ambiguity. See Tuple for an example of that.Kablesh
@macias: I don't consider the constructor to be generic, precisely because it doesn't introduce any extra type parameters. Would you consider List<T>.IndexOf to be a generic method, just because it's in a generic type?Kablesh
Peter -- actually the class name overloading is useful, because you put in non-generic class all static methods. In C++ I always have such odd pairs MyClass<...> and MyClassFactory. In C# you can have several Tuple classes and the call Tuple.Create(5,"hello",4.5) is "clean".Nimrod
@Jon, yes, I consider such method as generic. After all they all work on generic types -- thus they are generic. The non-generic one would be Count -- because it does not depend on generic type.Nimrod
M
35

Generic constructors are not supported, but you can get around this by simply defining a generic, static method that returns a new Foo:

class Foo
{
  public static Foo CreateFromFuncs<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

which is used like this:

// create generic dependencies
var func1 = new Func<byte, string>(...);
var func2 = new Func<string, byte>(...);

// create nongeneric Foo from dependencies
Foo myFoo = Foo.CreateFromFuncs<byte, string>(func1, func2);
Merge answered 31/8, 2010 at 7:39 Comment(0)
G
2

Here is an practical example about how you would like to have extra constructor type parameter, and the workaround.

I am going to introduce a simple RefCounted wrapper for IDisposable:

public class RefCounted<T> where T : IDisposable
{
    public RefCounted(T value)
    {
        innerValue = value;
        refCount = 1;
    }

    public void AddRef()
    {
        Interlocked.Increment(ref refCount);
    }

    public void Dispose()
    {
        if(InterlockedDecrement(ref refCount)<=0)
            innerValue.Dispose();
    }

    private int refCount;
    private readonly innerValue;
}

This seems to be fine. But sooner or later you would like to cast a RefCounted<Control> to RefCounted<Button> whilst keep both object reference counting, i.e. only when both instances being disposed to dispose the underlying object.

The best way is if you could write (like C++ people can do)

public RefCounted(RefCounted<U> other)
{
    ...whatever...
}

But C# does not allow this. So the solution is use some indirection.

private readonly Func<T> valueProvider;
private readonly Action disposer;

private RefCounted(Func<T> value_provider, Action disposer)
{
    this.valueProvider = value_provider;
    this.disposer = disposer;
}

public RefCounted(T value) : this(() => value, value.Dispose)
{
}

public RefCounted<U> Cast<U>() where U : T 
{
    AddRef();
    return new RefCounted<U>(() => (U)(valueProvider()),this.Dispose);
}

public void Dispose(){
    if(InterlockedDecrement(ref refCount)<=0)
        disposer();
}

If your class have any fields that are of generic type, you have no choice but to put all those types to the class. However, if you just wanted to hide some type from the constructor, you will need to use the above trick - having a hidden constructor to put everything together, and define a normal generic function to call that constructor.

Galata answered 24/7, 2014 at 2:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.