dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.71k stars 1.06k forks source link

Movement of clauses in an inheritance #43095

Open rqueizan opened 2 months ago

rqueizan commented 2 months ago

I'm trying to make the following inheritance.

public class A { public IEnumerable<T> Method<T>(IEnumerable<T> elements) where T : SomeInterface

public class B<T> : A where T : SomeInterface { public IEnumerable<T> Method(IEnumerable<T> elements)

It seems to me that semantically this inheritance is compatible and admissible since the type of data is the same and maintains the same clauses.

KalleOlaviNiemitalo commented 2 months ago

Sure, you can do that

using System;
using System.Collections.Generic;

public interface SomeInterface
{
}

public class A
{
    public IEnumerable<T> Method<T>(
        IEnumerable<T> elements)
        where T : SomeInterface
    {
        throw new NotImplementedException();
    }
}

public class B<T> : A
    where T : SomeInterface
{
    public IEnumerable<T> Method(
        IEnumerable<T> elements)
    {
        return this.Method<T>(elements);
    }
}

SharpLab

B\<T>.Method(IEnumerable\<T>) cannot override A.Method\<T>(IEnumerable\<T>), though.

rqueizan commented 2 months ago

Here's the specific example I'm testing:

`using Xunit; using Xunit.Abstractions; using Xunit.Sdk; public class CustomCaseOrderer1 : ITestCaseOrderer { public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase { foreach (var testCase in testCases) yield return testCase; } }

public class CustomCaseOrderer2 : ITestCaseOrderer where TTestCase : ITestCase { public IEnumerable OrderTestCases(IEnumerable testCases) { foreach (var testCase in testCases) yield return testCase; } }`

image

KalleOlaviNiemitalo commented 2 months ago

Xunit.Sdk.ITestCaseOrderer has a generic method IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase.

Consider the scenario

// Two different test-case types
struct TC1 : ITestCase { ... }
struct TC2 : ITestCase { ... }

// What you attempted to define
public class CustomCaseOrderer2<T> : ITestCaseOrderer
    where T : ITestCase
{
    public IEnumerable<T> OrderTestCases(IEnumerable<T> testCases)
    {
        foreach (var testCase in testCases)
            yield return testCase;
    }
}

class Program
{
    static void Main()
    {
        ITestCaseOrderer orderer = new CustomCaseOrderer2<TC1>();
        IEnumerable<TC2> cases = new TC2[] { new TC2() };

        // the type argument in OrderTestCases<TC2>
        // would be deduced but I'm writing it out
        // for clarity
        IEnumerable<TC2> result = orderer.OrderTestCases<TC2>(cases);
    }
}

ITestCaseOrderer must be able to order a sequence of any test-case type, but CustomCaseOrderer2\<TC1> can only order a sequence of TC1; it cannot order a sequence of TC2. That's why CustomCaseOrderer2\<T> cannot implement ITestCaseOrderer.

rqueizan commented 2 months ago

You are right. Putting it in the constructor limits the specific classes it can support, even if it respects the original definition to some extent.