Дженерики, наследование и отказавшее разрешение метода компилятора C#

Я работал над проектом в 2008 году, используя библиотеку стилей внедрения зависимостей, которая позволяла нам определять дизайн нашего приложения (в коде), используя внутренний язык, специфичный для предметной области (DSL).

Библиотека позволяет нам определять системы и составлять эти системы из других систем. Система представляла собой набор объектов, которые реализовали интерфейсы в области видимости. Система / подсистема может выбрать предоставление интерфейсов родительской области.

Эффектом этого было то, что миксин пришел бесплатно. Вы просто добавили бы класс, реализующий часть поведения, к определению вашей системы и предоставили бы его интерфейс родительской области видимости. Эта система теперь имеет такое поведение.

Возможно, вы сможете сделать это и с помощью современных систем внедрения зависимостей.

Мы использовали NDI ( https://github.com/NigelThorne/ndependencyinjection/wiki ).

Примечание: я написал NDI еще в 2008 году.

6
задан Community 23 May 2017 в 12:04
поделиться

6 ответов

Править

Хорошо ... Думаю, теперь я понимаю ваше замешательство. Вы ожидали, что DoStuff (строка) сохранит параметр как строку и сначала прошел по списку методов BaseClass в поисках подходящей сигнатуры, и в случае неудачи этого отката к попытке привести параметр к какому-либо другому типу.

Но это случилось наоборот ... Вместо Container.DoStuff (string) пошел, м-м, там есть метод базового класса, который соответствует всем требованиям, но я собираюсь преобразовать его в IEnumerable и у меня случится сердечный приступ о том, что доступно в текущем классе вместо этого ...

Хммм ... Я уверен, что Джон или Марк смогут вмешаться в этот момент с конкретным параграфом Спецификации C #, касающимся этого конкретного углового случая

Оригинал

Оба метода ожидают коллекцию IEnumerable

You ' ожидаем IEnumerable , поэтому я превратить эту строку в IEnumerable ... Готово

Верно, проверьте первый метод ... хммм ... этот класс Контейнер , но у меня есть IEnumerable так что это неправильно.

Проверьте второй метод, хммм .... Я есть IEnumerable , но char не реализует строку, поэтому тоже не правильно.

ОШИБКА КОМПИЛЯТОРА

Итак, какое # исправление, ну, это полностью зависит от того, чего вы пытаетесь достичь ... оба следующих утверждения будут действительными, по сути, ваше использование типов просто неверно в вашем воплощении .

        Container<char> c1 = new Container<char>();
        c1.DoStuff("Hello World");

        Container<string> c2 = new Container<string>();
        c2.DoStuff(new List<string>() { "Hello", "World" });
3
ответ дан 9 December 2019 в 22:39
поделиться

Как объяснил Эрик Липперт, компилятор выбирает метод DoStuff (IEnumerable ), где Tother: T {} , потому что он выбирает методы до проверки ограничений. Поскольку строка может выполнять IEnumerable <> , компилятор сопоставляет ее с методом этого дочернего класса. Компилятор работает правильно , как описано в спецификации C #.

Требуемый порядок разрешения методов может быть установлен путем реализации DoStuff как метода расширения . Методы расширения проверяются после методов базового класса, поэтому он не будет пытаться сопоставить строку с DoStuff IEnumerable , пока после он попытался сопоставить его с DoStuff .

Следующий код демонстрирует желаемый порядок разрешения метода, ковариацию и наследование. Скопируйте / вставьте его в новый проект.

Этот самый большой недостаток , о котором я могу думать, заключается в том, что вы не можете использовать base в методах переопределения, но я думаю, что есть есть способы обойти это (спросите, если вам интересно).

using System;
using System.Collections.Generic;

namespace MethodResolutionExploit
{
    public class BaseContainer<T> : IEnumerable<T>
    {
        public void DoStuff(T item) { Console.WriteLine("\tbase"); }
        public IEnumerator<T> GetEnumerator() { return null; }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; }
    }        
    public class Container<T> : BaseContainer<T> { }
    public class ContainerChild<T> : Container<T> { }
    public class ContainerChildWithOverride<T> : Container<T> { }
    public static class ContainerExtension
    {
        public static void DoStuff<T, Tother>(this Container<T> container, IEnumerable<Tother> collection) where Tother : T
        {
            Console.WriteLine("\tContainer.DoStuff<Tother>()");
        }
        public static void DoStuff<T, Tother>(this ContainerChildWithOverride<T> container, IEnumerable<Tother> collection) where Tother : T
        {
            Console.WriteLine("\tContainerChildWithOverride.DoStuff<Tother>()");
        }
    }

    class someBase { }
    class someChild : someBase { }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("BaseContainer:");
            var baseContainer = new BaseContainer<string>();
            baseContainer.DoStuff("");

            Console.WriteLine("Container:");
            var container = new Container<string>();
            container.DoStuff("");
            container.DoStuff(new List<string>());

            Console.WriteLine("ContainerChild:");
            var child = new ContainerChild<string>();
            child.DoStuff("");
            child.DoStuff(new List<string>());

            Console.WriteLine("ContainerChildWithOverride:");
            var childWithOverride = new ContainerChildWithOverride<string>();
            childWithOverride.DoStuff("");
            childWithOverride.DoStuff(new List<string>());

            //note covariance
            Console.WriteLine("Covariance Example:");
            var covariantExample = new Container<someBase>();
            var covariantParameter = new Container<someChild>();
            covariantExample.DoStuff(covariantParameter);

            // this won't work though :(
            // var covariantExample = new Container<Container<someBase>>();
            // var covariantParameter = new Container<Container<someChild>>();
            // covariantExample.DoStuff(covariantParameter);

            Console.ReadKey();
        }
    }
}

Вот результат:

BaseContainer:
        base
Container:
        base
        Container.DoStuff<Tother>()
ContainerChild:
        base
        Container.DoStuff<Tother>()
ContainerChildWithOverride:
        base
        ContainerChildWithOverride.DoStuff<Tother>()
Covariance Example:
        Container.DoStuff<Tother>()

Вы видите какие-либо проблемы с этой работой?

3
ответ дан 9 December 2019 в 22:39
поделиться

Компилятор попытается сопоставить параметр с IEnumerable . Тип String реализует IEnumerable , поэтому он предполагает, что T является "char".

После этого компилятор проверяет другое условие «where OtherT: T», и это условие не выполняется. Отсюда ошибка компилятора.

2
ответ дан 9 December 2019 в 22:39
поделиться

Моя УГАДАЯ, и это предположение, потому что я действительно не знаю, заключается в том, что он сначала смотрит в производный класс, чтобы разрешить вызов метода (потому что ваш объект является объектом производного типа ). Если и только если это невозможно, он переходит к рассмотрению методов базовых классов, чтобы решить эту проблему. В вашем случае, поскольку он МОЖЕТ разрешить его с помощью перегрузки

DoStuff <Tother>(IEnumerable<Tother> collection)

, он попытался втиснуть его в это. Таким образом, он МОЖЕТ разрешить это в том, что касается параметра, но тогда он сталкивается с препятствием в ограничениях. В этот момент он уже решил вашу перегрузку, поэтому дальше не смотрит, а просто выдает ошибку. Имеет смысл?

2
ответ дан 9 December 2019 в 22:39
поделиться

Я думаю, это как-то связано с тем фактом, что char - это тип значения, а string - ссылочный тип. Похоже, вы определяете

TOther : T

, а char не является производным от строки.

1
ответ дан 9 December 2019 в 22:39
поделиться

Я не совсем понимаю, чего вы пытаетесь достичь, что мешает вам использовать только два метода, DoStuff (элемент T) и DoStuff (коллекция IEnumerable )?

0
ответ дан 9 December 2019 в 22:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: