Is there a generic constructor with parameter constraint in C#?
Asked Answered
B

10

210

In C# you can put a constraint on a generic method like:

public class A {
    
    public static void Method<T> (T a) where T : new() {
        //...do something...
        return new T();
    }
    
}

Where you specify that T should have a constructor that requires no parameters. I'm wondering whether there is a way to add a constraint like "there exists a constructor with a float[,] parameter?"

The following code doesn't compile:

public class A {
    
    public static void Method<T> (T a) where T : new(float[,] u) {
        //...do something...
        return new T(new float[0,0]);
    }
    
}

A workaround is also useful?

Baras answered 5/12, 2009 at 17:37 Comment(2)
I think this question could be improved by changing Method in both examples to return an instance of T that is constructed inside each method body. By passing in T a it's not clear that the desire is to be able to create an instance of T.Andes
In the second example you could have static T Method<T>(float[,] a) => new T(a); I know I'm using more recent C# syntax than when you asked the question 14 years ago!!Andes
T
172

As you've found, you can't do this.

As a workaround I normally supply a delegate that can create objects of type T:

public class A {

    public static void Method<T> (T a, Func<float[,], T> creator) {
        //...do something...
    }

}
Trike answered 5/12, 2009 at 17:41 Comment(11)
are parameterized constructors constraints absent for a logical reason, or is it just something that has yet to be added to the language?Janiculum
Agreed...we should have new(float, double), new(string), etc.Anthe
@Sahuagin I think it is not possible to do because when you inherit from a class, there's no guarantee that the sub-class has that constructor is defined, as constructors are not inherited. Every class has an empty parameter constructor however.Timothea
@Timothea Not every class has parameterless constructor, if you define a constructor with parameters and dont redefine the default constructor, there's no default constructor.Foxworth
@Matthew, also if you inherit from a base class you're forced to create matching constructors (unless only the default constructor exists). At least in C# anywayDanish
@Timothea That's the whole point of generic type constraints. You require a class, which derives from some class and contains a constructor with specific parameters.Dayak
@TimRobinson would you provide a sample of calling the method?Jelena
@bc3tech, technically, your point is not 100% correct. If a base class has no default constructor, you are forced to provide a constructor that calls one of the base class constructors. You are not forced to provide a matching constructor. There is a subtle difference here...Tamie
Also the compiler will supply a default constructor if you don't provide one.Ullrich
@Foxworth There can only be one Short CircuitWestmoreland
I think it may have to do with supplying generic types again into those constructors and somehow ending up in infinite recursion or something, I'm sure they though of it, it seems so obvious.Antipas
S
61

Using reflection to create a generic object, the type still needs the correct constructor declared or an exception will be thrown. You can pass in any argument as long as they match one of the constructors.

Used this way you cannot put a constraint on the constructor in the template. If the constructor is missing, an exception needs to be handled at run-time rather than getting an error at compile time.

// public static object CreateInstance(Type type, params object[] args);

// Example 1
T t = (T)Activator.CreateInstance(typeof(T));
// Example 2
T t = (T)Activator.CreateInstance(typeof(T), arg0, arg1, arg2, ...);
// Example 3
T t = (T)Activator.CreateInstance(typeof(T), (string)arg0, (int)arg1, (bool)arg2);
Sorci answered 26/3, 2012 at 8:13 Comment(0)
P
47

There is no such construct. You can only specify an empty constructor constraint.

I work around this problem with lambda methods.

public static void Method<T>(Func<int,T> del) {
  var t = del(42);
}

Use Case

Method(x => new Foo(x));
Pressure answered 5/12, 2009 at 17:40 Comment(3)
There is no way to abstract the creation of Foo inside the Method?Showman
What if the user of the Method does Method(x => new Foo());? Is there anyway to ensure that the lambda should be like that?Showman
What's the benefit of providing a delegate in this situation instead of returning the int and letting the consumer wrap it? It feels like extra boilerplate for no gain.Mab
J
20

Here is a workaround for this that I personally find quite effective. If you think of what a generic parameterized constructor constraint is, it's really a mapping between types and constructors with a particular signature. You can create your own such mapping by using a dictionary. Put these in a static "factory" class and you can create objects of varying type without having to worry about building a constructor lambda every time:

public static class BaseTypeFactory
{
   private delegate BaseType BaseTypeConstructor(int pParam1, int pParam2);

   private static readonly Dictionary<Type, BaseTypeConstructor>
   mTypeConstructors = new Dictionary<Type, BaseTypeConstructor>
   {
      { typeof(Object1), (pParam1, pParam2) => new Object1(pParam1, pParam2) },
      { typeof(Object2), (pParam1, pParam2) => new Object2(pParam1, pParam2) },
      { typeof(Object3), (pParam1, pParam2) => new Object3(pParam1, pParam2) }
   };

then in your generic method, for example:

   public static T BuildBaseType<T>(...)
      where T : BaseType
   {
      ...
      T myObject = (T)mTypeConstructors[typeof(T)](value1, value2);
      ...
      return myObject;
   }
Janiculum answered 8/2, 2012 at 7:48 Comment(2)
I'm using this now, I think it's a good pattern. Works really well with the Factory pattern. Thanks!Timothea
This can be extended to create types based on other data as well. I often use this type of construct when parsing IFF-like files. I prefer defining static constructors on the type itself, so my dictionary entries end up looking like ["CELL"] = Cell.CreateInstance, ["WRLD"] = World.CreateInstance, ...Verbalize
D
9

No. At the moment the only constructor constraint you can specify is for a no-arg constructor.

Diachronic answered 5/12, 2009 at 17:39 Comment(0)
N
9

I think this is the most clean solution that kind of puts a constraint on the way an object is constructed. It is not entirely compile time checked. When you have the agreement to make the actual constructor of the classes have the same signature like the IConstructor interface, it is kind of like having a constraint on the constructor. The Constructor method is hidden when working normally with the object, because of the explicit interface implementation.

using System.Runtime.Serialization;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            var employeeWorker = new GenericWorker<Employee>();
            employeeWorker.DoWork();
        }
    }

    public class GenericWorker<T> where T:IConstructor
    {
        public void DoWork()
        {
            T employee = (T)FormatterServices.GetUninitializedObject(typeof(T));
            employee.Constructor("John Doe", 105);
        }
    }

    public interface IConstructor
    {
        void Constructor(string name, int age);
    }

    public class Employee : IConstructor
    {
        public string Name { get; private set; }
        public int Age { get; private set; }

        public Employee(string name, int age)
        {
            ((IConstructor)this).Constructor(name, age);
        }

        void IConstructor.Constructor(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
}
Nacre answered 5/4, 2018 at 14:19 Comment(1)
+1 for providing some compile-time safety that the others don't provide as well as for providing interface support where others don't.Mab
S
3

How about creating your generic class with constraints, here I chose struct and class to have value and reference types.

That way your constructor has a constraint on the values.

class MyGenericClass<T, X> where T : struct where X : class 
{
    private T _genericMemberVariableT;
    private X _genericMemberVariableX;

    public MyGenericClass(T valueT, X valueX)
    {
        _genericMemberVariableT = valueT;
        _genericMemberVariableX = valueX;
    }

    public T GenericMethod(T genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(), genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), _genericMemberVariableT);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(X).ToString(), _genericMemberVariableX);
        return _genericMemberVariableT;
    }
 
    public T GenericProperty { get; set; }
}

Implementation:

var intGenericClass = new MyGenericClass<int, string>(10, "Hello world");

int val = intGenericClass.GenericMethod(200);
Sangfroid answered 3/4, 2019 at 16:54 Comment(1)
I'm not sure this is really answering the question. The question could be improved to show how in the "... do something..." block, the questioner wants to create an instance of T by passing a value to T's constructor as a parameter. Your answer here doesn't address how to create a new instance of either of the generic parameters.Andes
R
3

Here's the recommended workaround by c# maintainers if you'd like to keep the constructor parameter-ful, call the constructor indirectly:

i = (TService)Activator.CreateInstance(
                  typeof(TService),
                  new object[] {arg}
              );

Where TService is a generic with a parameter-full constructor that I'd like to keep.

If you'd like to read up on how this method works: https://learn.microsoft.com/en-us/dotnet/api/system.activator.createinstance?view=net-5.0#system-activator-createinstance(system-type-system-object-)

Aaaaand discussion by maintainers of C#: https://github.com/dotnet/csharplang/discussions/769

Royal answered 9/7, 2021 at 23:20 Comment(1)
note that Activator.CreateInstance is quite slowSweptwing
P
3

As of C# 11 / .NET 7 this can be accomplished by applying a constraint on an interface containing a static abstract factory creation method with the necessary arguments, then implementing the interface in all relevant types.

E.g., first define the following interface:

public interface ICreatable<TArgument, TResult>
{
    public abstract static TResult Create(TArgument arg);
}

Then, in your Method<T>, if you would like T to have a static factory method that takes a 2d float array, constrain it as follows:

public class A 
{
    public static void Method<T> (T a) where T : ICreatable<float[,], T>
    {
        var t = T.Create(new [,] { { 1f, 2f }, {3f, 4f} });
        //...do something...        
    }
}

Of course, any type you pass into Method<T> will need to implement ICreatable<float[,], T>, e.g. as follows:

public partial class Matrix2DFloat : ICreatable<float[,], Matrix2DFloat>
{
    readonly float[,] array;
    public Matrix2DFloat(float[,] array) => this.array = array ?? throw new ArgumentNullException(nameof(array));

    #region ICreatable<float[,], Matrix2DFloat> Members

    public static Matrix2DFloat Create(float[,] arg) => new Matrix2DFloat(arg);

    #endregion
}

Demo fiddle here.

Pentarchy answered 21/10, 2023 at 18:55 Comment(0)
R
1

As alternative (from C# 9+), you can define a interface with "init" properties, as you would to arguments of a constructor. One major benefit is it works for structs or classes.

using System;
                    
public class Program
{
    public interface ITest
    {
        int a { init; }
    }
    public struct Test : ITest{
        public int a { private get; init; }
        public int b => a;
    }   
    public static T TestFunction<T>() where T: ITest, new() {
        return new(){ a = 123 };
    }
    public static void Main()
    {
        var t = TestFunction<Test>();
        Console.WriteLine($"Hello World: {t.b}"); // Prints: Hello World: 123
    }
}
Reagent answered 16/2, 2023 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.