'Type' cannot satisfy the 'new()' constraint on parameter 'TParam' because 'Type' has required members
Asked Answered
V

4

8

I have this class structure (simplified):

public class InducingMedium
{
   public required string File { get; set; }
}

public class InducingVideo : InducingMedium {}
public class InducingAudio : InducingMedium {}

Now, I want to generically instantiate an instance of a specific type:

public abstract class BaseInducingTests<TMedium>
    where TMedium : InducingMedium, new()
{
   protected async Task<IEnumerable<TMedium>> CreateInducingMedia(IEnumerable<string> files)
   {
      return files.Select(file =>
      {
          // Do some processing...

          return new TMedium
          {
              File = file,
          };
      });
   }
}

public class InducingVideosTests : BaseInducingTests<InducingVideo>
{
}

But in the derived class I get an error:

'Namespace.InducingVideo' cannot satisfy the 'new()' constraint 
on parameter 'TMedium' in the generic class 'Namespace.Tests.BaseInducingTests<TMedium>' 
because 'Namespace.InducingVideo' has required members

Is there any way to fix this without introducing reflection?
I was really excited about required members, which work pretty well with nullable types, but now I see this has its own caveats :(

Virago answered 28/1, 2023 at 9:44 Comment(0)
A
5

This is explicitly mentioned in the docs:

A type with any required members may not be used as a type argument when the type parameter includes the new() constraint. The compiler can't enforce that all required members are initialized in the generic code.

Either remove the required modifier or change the generic handling. For example provide factory via ctor:

public abstract class BaseInducingTests<TMedium>
    where TMedium : InducingMedium
{
    private readonly Func<string, TMedium> _init;

    public BaseInducingTests(Func<string, TMedium> init)
    {
        _init = init;
    }
    protected async Task<IEnumerable<TMedium>> CreateInducingMedia(IEnumerable<string> files)
    {
        return files.Select(file => _init(file));
    }
}

public class InducingVideosTests : BaseInducingTests<InducingVideo>
{
    public InducingVideosTests() : base(s => new InducingVideo{File = s})
    {
    }
}

If needed you can then create a wrapper to support classes which satisfy new() constraint:

public abstract class BaseNewableInducingTests<TMedium> : BaseInducingTests<TMedium>
    where TMedium : InducingMedium, new()
{
    protected BaseNewableInducingTests() : base(s => new TMedium { File = s })
    {
    }
}
Abolition answered 28/1, 2023 at 9:53 Comment(0)
V
6

Another workaround which I've just found is SetsRequiredMembers attribute:

public class InducingVideosTests : BaseInducingTests<TestInducingVideo>
{
}

public class TestInducingVideo : InducingVideo
{
    [SetsRequiredMembers]
    public TestInducingVideo()
    {
    }
}

This kind of defeats the purpose of required members, but so does reflection and for test purposes it's an easier way.

Source: https://code-maze.com/csharp-required-members/

Virago answered 28/1, 2023 at 10:3 Comment(1)
Note - this is also mentioned in the docs (referenced in my answer) with a warning: "The SetsRequiredMembers disables the compiler's checks that all required members are initialized when an object is created. Use it with caution.", one reason being - it does not even check if ctor actually does what is claimed (i.e. sets File in this case).Abolition
A
5

This is explicitly mentioned in the docs:

A type with any required members may not be used as a type argument when the type parameter includes the new() constraint. The compiler can't enforce that all required members are initialized in the generic code.

Either remove the required modifier or change the generic handling. For example provide factory via ctor:

public abstract class BaseInducingTests<TMedium>
    where TMedium : InducingMedium
{
    private readonly Func<string, TMedium> _init;

    public BaseInducingTests(Func<string, TMedium> init)
    {
        _init = init;
    }
    protected async Task<IEnumerable<TMedium>> CreateInducingMedia(IEnumerable<string> files)
    {
        return files.Select(file => _init(file));
    }
}

public class InducingVideosTests : BaseInducingTests<InducingVideo>
{
    public InducingVideosTests() : base(s => new InducingVideo{File = s})
    {
    }
}

If needed you can then create a wrapper to support classes which satisfy new() constraint:

public abstract class BaseNewableInducingTests<TMedium> : BaseInducingTests<TMedium>
    where TMedium : InducingMedium, new()
{
    protected BaseNewableInducingTests() : base(s => new TMedium { File = s })
    {
    }
}
Abolition answered 28/1, 2023 at 9:53 Comment(0)
Z
1

I started getting this error in Visual Studio Version 17.9.5 because Visual Studio automatically added required on save. This is due to Code Cleanup.

Error CS9040 'FilesResponse' cannot satisfy the 'new()' constraint on parameter 'T' in the generic type or or method 'MyCode' because 'FilesResponse' has required members.

Solved this by simply setting the class as nullable in cases where a new instance did not have every variable. In your case this would be:

public class InducingMedium
{
   public string? File { get; set; }
}
Zealous answered 11/4 at 9:28 Comment(0)
D
0

Credit to this GitHub post where the author proposed "Allowing new() generic constraint to have some required members if the derived class has no extra required members".

You cannot know if the inheritance tree of a type is complete at compile-time. Assemblies may be dynamically loaded at runtime.

In the same post, a workaround is also proposed instead of using new(), use the new C# 11 static abstract interface member:

public abstract class DefaultComponentParts
{
    public required string Name { get; set; }
    public string? Description { get; set; }
}

public class ComponentCssProperty : DefaultComponentParts, INew<ComponentCssProperty>
{
    public static ComponentCssProperty New(string name)
        => new() { Name = name, };
}

public class ComponentSlot : DefaultComponentParts, INew<ComponentSlot>
{
    public static ComponentSlot New(string name)
        => new() { Name = name, };
}

public interface INew<T> where T : DefaultComponentParts, INew<T>
{
    static abstract T New(string name);
}

// Usage:
    static List<T> ParseComponentParts<T>(JsonObject declarations, string partName)
        where T : DefaultComponentParts, INew<T>
    {
        T.New(name) // When you need the T
    }
Deitz answered 25/7 at 10:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.