Как стать автором
Обновить

Утипизация в C#

Время на прочтение5 мин
Количество просмотров19K
Многогранный Шерлок Холмс и Эраст Фандорин, идеальный аристократ, очень чтили дедуктивный метод и оба достигли в его применении потрясающих успехов. «Отбросьте все невозможное, то, что останется, и будет ответом, каким бы невероятным он ни казался» — так говорил сэр Артур Конан Дойль устами своего героя.

Однако же, на дедуктивных умозаключениях наука о рассуждениях не оканчивается — не стоит забывать еще и о индукции. О ней и о приближенных материях и будет трактовать сей трактат.

Тест на утку


Индукция — это процесс логических умозаключений, основывающийся на переходе от частных положений к общему. Известный «тест на утку» является ярким примером подобного процесса:

If it walks like a duck and quacks like a duck, it must be a duck
(Если что-то ходит, как утка, и крякает, как утка, то это утка)


Применительно к языкам программирования — в частности, к объектно-ориентированным — «тест на утку» приобретает особый смысл. В динамически типизированных языках семантика использования того или иной объекта определяется текущим набором его методов и свойств. К примеру, в статически типизированном языке, не поддерживающем утипизацию, можно создать функцию, принимающую в качестве параметра объект класса Duck и вызывающую методы Quack и Walk этого объекта. В утипизированном же (и, как следствие, в динамически типизированном) языке аналогичная функция может принимать в качестве параметра некий абстрактный объект и попытаться вызывать у него те же методы (возможно, проверив их наличие). В статически типизированном языке подобного поведения можно достигнуть с использованием наследования и реализации интерфейсов, но это будет выражением формы is-a, в отличие от has-a в динамических языках.

Вкратце объяснив, что же такое утипизация и почему она недоступна в статически типизированных языках, я сейчас слегка опровергну сам себя. Для кого-то, возможно, это не будет откровением, но тем не менее. Некое подобие утиной типизации есть и в C#. Вот об этом сейчас поподробней.

foreach


Синтаксический сахар может быть чуть слаще, чем кажется на первый взгляд. В частности, это касается оператора foreach. В большинстве учебников сказано, что для поддержки семантики foreach в классе должен быть реализован (явно или неявно) интерфейс System.Collections.IEnumerable. На деле же это не является до конца верным утверждением — если говорить о C#, то упомянутый интерфейс реализовывать совершенно необязательно.

Если открыть C# Language Specification и заглянуть в раздел 8.8.4, то можно увидеть следующее:

The type of the expression of a foreach statement must be a collection type (as defined below), and an explicit conversion (§6.2) must exist from the element type of the collection to the type of the iteration variable. If expression has the value null, a System.NullReferenceException is thrown.
A type C is said to be a collection type if it implements the System.Collections.IEnumerable interface or implements the collection pattern by meeting all of the following criteria:
  • C contains a public instance method with the signature GetEnumerator() that returns a struct-type, class-type, or interface-type, which is called E in the following text.
  • E contains a public instance method with the signature MoveNext() and the return type bool.
  • E contains a public instance property named Current that permits reading the current value. The type of this property is said to be the element type of the collection type.

Таким образом, следующий код прекрасно скомпилируется и выведет на консоль то, что ему положено:

using System;
using System.Collections.Generic;

namespace DuckTyping
{
  class Collection
  {
    public Enumerator GetEnumerator()
    {
      return new Enumerator();
    }
  }

  class Enumerator
  {
    private bool moveNext = true;

    public object Current
    {
      get { return "Enumerator.Current"; }
    }

    public bool MoveNext()
    {
      if(!moveNext)
        return false;

      moveNext = false;
      return true;
    }
  }

  class Program
  {
    static void Main()
    {
      foreach(object o in new Collection())
        Console.WriteLine(o);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.

Мелочь, я приятно.

Collection Initializers


Довольно приятное нововведение C# 3.0. Напомню:

var digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

* This source code was highlighted with Source Code Highlighter.

Все бы ничего, но осталось определить термин «Collection». И вот с этим возникли проблемы.

Интерфейс System.Collections.ICollection до .NET 2.0 был совершенно бестолковым созданием. В 2.0 его починили: System.Collections.Generic.ICollection<T> позволяет добавлять и удалять элементы, проходиться по ним упомянутым уже foreach и узнавать число элементов в коллекции.

Казалось бы, решение найдено: любой класс, реализующий ICollection<T> и будет называться коллекцией. Но не тут-то было — его практически никто не реализует. Из тысяч классов .NET BCL всего лишь несколько десятков могут похвастаться тем, что у них есть общедоступный конструктор без параметров и их можно привести к ICollection<T>.

Сдаваться в C# Design Group никто не думал. Было решено использовать уже хорошо зарекомендовавший себя Pattern-based syntax — а именно, вызывать метод Add(), если оный вообще есть. Теперь требования к коллекции с точки зрения Collection Initializer следующие: реализация IEnumerable и наличие хотя бы одного общедоступного метода Add().

Пункт «наличие хотя бы одного...» требует небольшого пояснения. Заключенный в фигурные скобки список элементов не является «списком элементов, которые надо добавить» — это «список аргументов для методов Add()». Список аргументов, таким образом, может быть гетерогенным. Другими словами, имея такой класс:

public class Plurals : IDictionary<string,string>
{
  public void Add(string singular, string plural);
  public void Add(string singular);
  public void Add(KeyValuePair<string,string> pair);
}


* This source code was highlighted with Source Code Highlighter.

вполне легальным будет следующий инициализатор:

var myPlurals = new Plurals{ “collection”, { “query”, “queries” }, new KeyValuePair(“child”, “children”) };

* This source code was highlighted with Source Code Highlighter.

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

Вместо заключения


Шерлок Холмс, Эраст Петрович и ваш покорный слуга раскланиваются. Надеюсь, было интересно.

Спасибо Dr. T за пищу для размышлений.
Теги:
Хабы:
Всего голосов 39: ↑36 и ↓3+33
Комментарии20

Публикации