0

Iam trying to make a generic class that receives a Type. this generic class will need to create an instance from the received type. The received type has two overloads in his constructor, one constructor can receive a parameter but the other one doesn't have any parameter. y generic class need sometimes to create object from received class without parameters in constructor and other times with parameter in constructor.

A simple view in my generic class :

public sealed class Repo<TContext> : IRepo<TContext>, IDisposable
    where TContext : DbContext, new()
{
    #region properties

    /// <summary>
    /// Private DBContext property
    /// </summary>
    private DbContext _Context { get; } = null;


    /// <summary>
    /// Determine if Lazy Loading either activate or not
    /// </summary>
    private bool _LazyLoaded { get; set; }

    #endregion

    #region Construcors

    public Repo(bool LazyLoaded)
    {
        _Context                                  = new TContext();
        _LazyLoaded                               = LazyLoaded;
        _Context.ChangeTracker.LazyLoadingEnabled = LazyLoaded;
    }

    public Repo(DbContext context,bool LazyLoaded)
    {
        _Context                                  = context;
        _LazyLoaded                               = LazyLoaded;
        _Context.ChangeTracker.LazyLoadingEnabled = LazyLoaded;
    }

at now everything's good, but when I add a third constructor in my generic class for creating an instance from received TContext but this time with his (TContext) constructor that need one parameter,

public Repo(DbContextOptionsBuilder<TContext> optionsBuilder,bool LazyLoaded)
{
    _Context                                  = new TContext(optionsBuilder);
    _LazyLoaded                               = LazyLoaded;
    _Context.ChangeTracker.LazyLoadingEnabled = LazyLoaded;
}

I got this error:

Error CS0417 'TContext': cannot provide arguments when creating an instance of a variable type MyTypeName

The Question:

My question is how I can create an instance from TContext using his constructor that receive parameters ?

thank you in advance.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
XDev
  • 125
  • 1
  • 8
  • 1
    You can't use `new()` to do this. You could use reflection to create the new object, but that makes your code a little fragile. It would probably be better to pass in some kind of factory method that created the object. – DavidG Feb 07 '21 at 16:45
  • As @DavidG says you're probably better off with a factory class although you'd need a factory class for each `DbContext` type you want to build. I'm wondering though... why do you need 3 ways to receive a `DbContext`? Something seems fishy in your setup. – Xerillio Feb 07 '21 at 16:49
  • Just a heads up even with the `new()` constraint, .net will use the following IL Code `[System.Runtime]System.Activator::CreateInstance<!!0/*T*/>()` – Hasan Emrah Süngü Feb 07 '21 at 17:31

2 Answers2

0

That doesn't work, at least not out of the box. There's no way to provide a constraint that indicates that the template argument must have a ctor with a specific signature (other than new(), which defines that the template argument must have a ctor with 0 arguments).

You could either provide the extra parameter as property instead (and add a constraint to a corresponding interface), or use reflection to do this:

_Context = Activator.CreateInstance(typeof(TContext), optionsBuilder);

Of course, this has the downside of not being type safe any more. If TContext does not have a ctor taking the right argument type(s), you'd be getting an exception at runtime.

Seing the comment above, there's a third variant: Providing a factory interface:

public interface Creator<T>
{
   T Create();
   T Create(somearguments...);
}

And then you provide an instance of this interface to your Repo constructor.

PMF
  • 14,535
  • 3
  • 23
  • 49
  • First off massive thanks sir for your time and effort, I tried `_Context = Activator.CreateInstance(typeof(TContext), optionsBuilder);`, but I got an exception : `Constructor on type not found`, please can you provide me a good solution with a simple example and I appreciate this for you – XDev Feb 07 '21 at 16:50
  • @XDev: To help you there, I would need to know the definition of the class you use as TContext. That one now needs to have a ctor with `DbContextOptionsBuilder optionsBuilder` as argument. – PMF Feb 07 '21 at 16:53
  • Thinking about it... You might probably first check that this is even the right argument type for the context. – PMF Feb 07 '21 at 16:55
  • This is the definition for a dbcontext example `public class TrainContext:DbContext { public TrainContext(DbContextOptions contextOptions) : base(contextOptions) { } public TrainContext() { } #region DB Sets public virtual DbSet Client { get; set; } public virtual DbSet Order { get; set; } #endregion }` – XDev Feb 07 '21 at 16:56
  • yeah I am sure that `DbContextOptionsBuilder` is the right argument for my contexts – XDev Feb 07 '21 at 16:57
  • Yea, this looks right. You might need to try one of the other overloads of `CreateInstance`. The behavior of that method might sometimes be a bit confusing (I often also need to debug this in detail) – PMF Feb 07 '21 at 17:49
0

One good option that does not involve reflection, is to use a factory functor/lambda.

public Repo(
    DbContextOptionsBuilder<TContext> optionsBuilder,
    bool LazyLoaded,
    Func<DbContextOptionsBuilder<TContext>, TContext> factory)
{
    _Context                                  = factory(optionsBuilder);
    _LazyLoaded                               = LazyLoaded;
    _Context.ChangeTracker.LazyLoadingEnabled = LazyLoaded;
}
Charlieface
  • 52,284
  • 6
  • 19
  • 43