Some C# 11&12 Practicing failed

156 views Asked by At

I am practicing a C# 11&12 new features so I have written an interface and a class like this:

public interface IHello<T> where T: class 
{
    static abstract IHello<T> operator +(IHello<T> left, IHello<T> right);
    
    public string Value { get; }
}
    
public class Hello(string value) : IHello<string> // Error CS0535 'Hello' does not implement interface member 'IHello<string>.operator +(IHello<string>, IHello<string>)'
{
    public string Value { get; } = value;
    
    public static IHello<string> operator +(Hello left, IHello<string> right)
    {
        return new Hello(left.Value + right.Value);
    }
}

However, on the class definition I get an error:

Error CS0535 'Hello' does not implement interface member 'IHello.operator +(IHello, IHello)'

If I change the operator signature to directly match the IHello<string> interface being implemented:

public class Hello(string value) : IHello<string>
{
    public string Value { get; } = value;

    public static IHello<string> operator +(IHello<string> left, IHello<string> right) // Error CS0563 One of the parameters of a binary operator must be the containing type
    {
        throw new NotImplementedException();
    }
}

The error then becomes:

Error CS0563 One of the parameters of a binary operator must be the containing type

Is there a way to reconcile between the two and implement this interface?

3

There are 3 answers

2
Good Night Nerd Pride On BEST ANSWER

Taking the implementation type as a type paramter in the interface allows you define on operator parameter as an implementation type:

public interface IHello<TSelf, T>
    where T: class 
    where TSelf: IHello<TSelf, T>
{
    public T Value { get; }
    static abstract TSelf operator +(TSelf left, IHello<TSelf, T> right);
}

public class Hello(string value) : IHello<Hello, string>
{
    public string Value { get; } = value;
    public static Hello operator +(Hello left, IHello<Hello, string> right)
        => new Hello(left.Value + right.Value);
}

Usage:

var h1 = new Hello("hello");
var h2 = new Hello("world");
var h3 = h1 + h2;
Debug.Assert(h3.Value == "helloworld");

The newer static abstract math stuff does this extensively. Check the type parameters in the interfaces of INumber<TSelf>.

0
Sweeper On

You can write an explicit interface implementation to avoid "Error CS0563 One of the parameters of a binary operator must be the containing type".

static IHello<string> IHello<string>.operator +(IHello<string> left, IHello<string> right)
    => new Hello(left.Value + right.Value);

But with an interface like this, all the implementation has are two IHello<string>s. From how you implemented Hello, it seems like you want the two parameters of + to be:

  1. Hello, or whatever type is implementing the interface, so that the operator can make use of members unique to Hello

  2. IHello<T>, so that you can "add" any other implementation of IHello<T> to Hello. e.g.

    // this would produce an instance of Hello new Hello("") + new AnotherImplementation() // class AnotherImplementation: IHello

To do that, you should use the Curiously Recurring Template Pattern. IHello should have an extra type parameter that represents "whatever type is implement thing interface", usually called TSelf.

There should still be a single-type-parameter IHello<T>. Otherwise what is going to be the second parameter type of +?

public interface IHello<T> {
    // move everything else in IHello<TSelf, T> here, except the operator
    string Value { get; }
}

public interface IHello<TSelf, T> : IHello<T>
    where T: class 
    where TSelf: IHello<TSelf, T>
{
    static abstract TSelf operator +(TSelf left, IHello<T> right);
}

public class Hello(string value) : IHello<Hello, string>
{
    public string Value { get; } = value;
    
    public static Hello operator +(Hello left, IHello<string> right)
        => new Hello(left.Value + right.Value);
}

If you simply just wanted the operator to only add up two of the same implementation of IHello<T>, use TSelf for both parameters of +.

public interface IHello<TSelf, T> 
    where T: class
    where TSelf: IHello<TSelf, T>
{
    static abstract TSelf operator +(TSelf left, TSelf right);
    
    public string Value { get; }
}
    
public class Hello(string value) : IHello<Hello, string>
{
    public string Value { get; } = value;
    
    public static Hello operator +(Hello left, Hello right)
        => new Hello(left.Value + right.Value);
}

See also how the operators of IAdditionOperators are declared. They all use the TSelf type parameter.

0
shingo On

Please note that the main purpose of static virtual members in interfaces is:

Once you've defined interfaces with static members, you can use those interfaces as constraints to create generic types that use operators or other static methods.

However, what you are doing now is trying to define the addition of a class, and the starting points of them are different. Don't confuse them.

public class Hello(string value) : IHello<string>
{
    public string Value { get; } = value;

    // the addition of a class
    public static Hello operator +(Hello left, Hello right)
        => new Hello(left.Value + right.Value);

    // static virtual members
    static IHello IHello.operator +(IHello left, IHello right)
        => left is Hello lh && right is Hello rh ? lh + rh : throw new ArgumentException();
}

public interface IHello
{
    static abstract IHello operator +(IHello left, IHello right);
}

public interface IHello<T> : IHello
{
    public T Value { get; }
}

And the abstract operator should be used in a generic class:

public class Test<T> where T : IHello
{
    public static void Add(T u1, T u2)
        => Console.WriteLine(u1 + u2);
}