2
public abstract record Result<T, ErrorT>
{
    private Result() { }
    public sealed record Ok(T result) : Result<T, ErrorT>;
    public sealed record Error(ErrorT error) : Result<T, ErrorT>;
}

public interface IValid<T>
{
    T Value { get; init; }
    abstract static IEnumerable<string> Validate(T obj);
    abstract static Result<IValid<T>, IEnumerable<string>> Create(T value);
}

I am trying to create a validation pattern that creates validated values of type T. But the Create function throws a compiler error CS8920

The interface Valid cannot be used as type argument

Any reason why this is not allowed and is there a way around this?

Example usage of this would be

public record DriverLicense : IValid<string>
{
    public string Value { get; init; } = null!;

    private DriverLicense() { }

    public static Result<DriverLicense, IEnumerable<string>> Create(string licenseNumber){...}
    public static IEnumerable<string> Validate(string licenseNumber){...}
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Xiaoguo Ge
  • 2,177
  • 20
  • 26
  • Firstly, it is convention to use an "I" prefix on interfaces and you really ought to abide by that. It will confuse a lot of people if you don't name that interface `IValid`. – user18387401 Jul 31 '22 at 04:02
  • @user18387401 good point. Changed it – Xiaoguo Ge Jul 31 '22 at 04:04
  • Secondly, generic type parameters should always start with "T", not end with "T", so should use `TError` rather than `ErrorT`. Also, if you are using multiple generic type parameters then you should use descriptive names for all of them, so none of them should be named just `T`. – user18387401 Jul 31 '22 at 04:05
  • 1
    Does [this](https://stackoverflow.com/questions/69238213/adding-operator-support-to-interfaces-preview-feature-in-net-6) answer your question? You need to apply the curiously recurring template pattern. – Sweeper Jul 31 '22 at 04:19
  • I can't test your code because I haven't installed .NET 7 preview. My best advice is to click the error code in the Error List window and see whether it provides some useful information. As your code is .NET 7-specific, you probably ought to add that tag, although I'm not sure that the issue itself is .NET 7-specific. – user18387401 Jul 31 '22 at 04:20
  • @Sweeper thanks, that answers my question. This works `abstract static Result> Create(T value) where TConcrete : IValid, new();` Still not clear why C# has this restriction though. Is it just too difficult or something fundamental? – Xiaoguo Ge Jul 31 '22 at 04:44

1 Answers1

3

This code alone:

public abstract record Result<T, ErrorT>
{
    private Result() { }
    public sealed record Ok(T result) : Result<T, ErrorT>;
    public sealed record Error(ErrorT error) : Result<T, ErrorT>;
}
public interface IValid<T>
{
    T Value { get; init; }
    abstract static IEnumerable<string> Validate(T obj);
    abstract static Result<IValid<T>, IEnumerable<string>> Create(T value);
}

did compile when they first added static abstract interface members, as can be seen on SharpLab, if you select the "C# Next: Static Abstract Members In Interfaces" branch.

However, this signature for Create is not what you actually mean. Your intended implementation doesn't actually implement Create:

// interface
abstract static Result<IValid<T>, IEnumerable<string>> Create(T value);
// implementation
public static Result<DriverLicense, IEnumerable<string>> Create(string licenseNumber){}

Notice that the return types are different, unrelated types. The type parameters of records are invariant. This would not have compiled in any version of C#.

What you actually meant is:

public interface IValid<TSelf, T> where TSelf: IValid<TSelf, T>
{
    ...
    abstract static Result<TSelf, IEnumerable<string>> Create(T value);
}

public record DriverLicense : IValid<DriverLicense, string>
{
    ...
    public static Result<DriverLicense, IEnumerable<string>> Create(string licenseNumber) {...}
}

See also:

My guess is that they changed it in a later version, so that an error message would now show up as soon as you try to write the incorrect interface, rather than show the "does not implement this method" error when you write the implementation later down the line.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks. Interesting that the interface definition compiles in sharplab but does not compile in my VS2022 Preview 5.0 + dotnet 7.0.100-preview.6.22352.1. Yes, the concrete Create function does not implement the interface. I've changed the interface to `abstract static Result> Create(T value) where TConcrete : IValid, new();` based on suggestion in the comments. – Xiaoguo Ge Jul 31 '22 at 05:17
  • @XiaoguoGe if this answers your question you are recommended to accept it – yehsuf Jul 31 '22 at 13:19