Я работал над проектом в 2008 году, используя библиотеку стилей внедрения зависимостей, которая позволяла нам определять дизайн нашего приложения (в коде), используя внутренний язык, специфичный для предметной области (DSL).
Библиотека позволяет нам определять системы и составлять эти системы из других систем. Система представляла собой набор объектов, которые реализовали интерфейсы в области видимости. Система / подсистема может выбрать предоставление интерфейсов родительской области.
Эффектом этого было то, что миксин пришел бесплатно. Вы просто добавили бы класс, реализующий часть поведения, к определению вашей системы и предоставили бы его интерфейс родительской области видимости. Эта система теперь имеет такое поведение.
Возможно, вы сможете сделать это и с помощью современных систем внедрения зависимостей.
Мы использовали NDI ( https://github.com/NigelThorne/ndependencyinjection/wiki ).
Примечание: я написал NDI еще в 2008 году.
Править
Хорошо ... Думаю, теперь я понимаю ваше замешательство. Вы ожидали, что 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" });
Как объяснил Эрик Липперт, компилятор выбирает метод DoStuff
, потому что он выбирает методы до проверки ограничений. Поскольку строка может выполнять 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>()
Вы видите какие-либо проблемы с этой работой?
Компилятор попытается сопоставить параметр с IEnumerable
. Тип String реализует IEnumerable
, поэтому он предполагает, что T является "char".
После этого компилятор проверяет другое условие «where OtherT: T», и это условие не выполняется. Отсюда ошибка компилятора.
Моя УГАДАЯ, и это предположение, потому что я действительно не знаю, заключается в том, что он сначала смотрит в производный класс, чтобы разрешить вызов метода (потому что ваш объект является объектом производного типа ). Если и только если это невозможно, он переходит к рассмотрению методов базовых классов, чтобы решить эту проблему. В вашем случае, поскольку он МОЖЕТ разрешить его с помощью перегрузки
DoStuff <Tother>(IEnumerable<Tother> collection)
, он попытался втиснуть его в это. Таким образом, он МОЖЕТ разрешить это в том, что касается параметра, но тогда он сталкивается с препятствием в ограничениях. В этот момент он уже решил вашу перегрузку, поэтому дальше не смотрит, а просто выдает ошибку. Имеет смысл?
Я думаю, это как-то связано с тем фактом, что char - это тип значения, а string - ссылочный тип. Похоже, вы определяете
TOther : T
, а char не является производным от строки.
Я не совсем понимаю, чего вы пытаетесь достичь, что мешает вам использовать только два метода, DoStuff (элемент T) и DoStuff (коллекция IEnumerable