Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

77%
+5 −0
Q&A F-bounded polymorphism, interface operators, and type inference in C#

C# language version 8.0 introduces limited support for static methods, operators, etc. in interfaces. However, there are still limitations. I was hoping to use the new language features to try a ge...

0 answers  ·  posted 3y ago by Peter Taylor‭

#1: Initial revision by user avatar Peter Taylor‭ · 2021-02-10T10:24:50Z (about 3 years ago)
F-bounded polymorphism, interface operators, and type inference in C#
C# language version 8.0 introduces limited support for static methods, operators, etc. in interfaces. However, there are still limitations. I was hoping to use the new language features to try a generic approach to abstract algebra with proper operator overloading, but I'm running into the limitations.

Ideally what I'd like is

    public interface IRing<TRing> where TRing : IRing<TRing>
    {
        public static TRing operator -(TRing a);
        public static TRing operator +(TRing a, TRing b);
        public static TRing operator -(TRing a, TRing b) => a + -b;
        public static TRing operator *(TRing a, TRing b);
    }

which can be used as e.g.

    static TRing EvaluatePolynomial<TRing>(TRing x, params TRing[] coeffsBE)
        where TRing : IRing<TRing>
    {
        var y = x - x; // Initially zero
        foreach (var coeff in coeffsBE)
            y = y * x + coeff;
        return y;
    }


There are two obvious problems: static members of interfaces need implementations, and operator overloads must use the enclosing type. The first is easy enough to work around:

    public interface IRing<TRing> where TRing : IRing<TRing>
    {
        TRing Negate();
        TRing Add(TRing addend);
        TRing Mul(TRing addend);

        public static TRing operator -(TRing a) => a.Negate(b);
        public static TRing operator +(TRing a, TRing b) => a.Add(b);
        public static TRing operator -(TRing a, TRing b) => a + -b;
        public static TRing operator *(TRing a, TRing b) => a.Mul(b);
    }

But the second is problematic. If I try changing the type of the first argument of each operator overload to `IRing<TRing>`:

    public interface IRing<TRing> where TRing : IRing<TRing>
    {
        TRing Negate();
        TRing Add(TRing addend);
        TRing Mul(TRing multiplicand);

        public static TRing operator -(IRing<TRing> a) => a.Negate();
        public static TRing operator +(IRing<TRing> a, TRing b) => a.Add(b);
        public static TRing operator -(IRing<TRing> a, TRing b) => a + -b;
        public static TRing operator *(IRing<TRing> a, TRing b) => a.Mul(b);
    }

then the generic `EvaluatePolynomial` compiles, but operations on specific implementations don't necessarily. This simple implementation:

    public struct BigInt : IRing<BigInt>
    {
        private BigInt(BigInteger value) { Value = value; }

        public static implicit operator BigInt(int value) => new BigInt(value);

        private readonly BigInteger Value;

        public BigInt Negate() => new BigInt(-Value);
        public BigInt Add(BigInt addend) => new BigInt(Value + addend.Value);
        public BigInt Mul(BigInt multiplicand) => new BigInt(Value * multiplicand.Value);

        public static void Test()
        {
            BigInt one = 1;
            BigInt negOne = -one;
            var two = one + one;
        }
    }

gives me errors on `-one` and `one + one` because, apparently, the type inference doesn't recognise that `BigInt` can be implicitly coerced to `IRing<BigInt>` and so match the signatures of the operator overloads.

**Is there a straightforward solution to this which doesn't require reimplementing the operator overloads in each implementing class or explicitly coercing `BigInt` to `IRing<BigInt>`?**