Создание экземпляра абстрактного класса в «С# в глубине»

В настоящее время я читаю «С# в деталях» Джона Скита, и есть пример, изображающий контракты кода с абстрактным классом, реализующим интерфейс, который является сопутствующим классом для интерфейса, в терминах контрактов кода: «Класс контракта для» ( Я не буду вдаваться в подробности о работе Code Contracts здесь).

Интерфейс (стр. 467):

[ContractClass(typeof(ICaseConverterContracts))]
public interface ICaseConverter
{
    string Convert(string text);
}

Абстрактный класс:

[ContractClassFor(typeof(ICaseConverter))]
internal abstract class ICaseConverterContracts : ICaseConverter
{
    public string Convert(string text)
    {
         Contract.Requires(text != null);
         Contract.Ensures(Contract.Result<string>() != null);
         return default(string); // returns dummy value
    }

    // prevents instantiation
    private ICaseConverterContracts() { }

}

(Я добавил комментарии в код на основе комментариев в книге)

Мой вопрос:

Почему необходимо добавлять частный конструктор в этот абстрактный класс, если вы не можете создать экземпляр абстрактного класса с самого начала? Что я не получаю?


person Aage    schedule 22.10.2013    source источник
comment
Действительно, это озадачивает; Абстрактные конструкторы должны/могут быть защищены; Может быть, сделать это явным? Тем не менее, быть абстрактным классом достаточно явно.... ждем дополнительных комментариев   -  person Luis Filipe    schedule 22.10.2013
comment
Не сомневайся в книгах Джона Скита!   -  person Uwe Keim    schedule 22.10.2013
comment
Спрашивал себя, а не Джона Скита ... Отсюда «Чего я не получаю?» :-)   -  person Aage    schedule 22.10.2013
comment
Очень надеялся, что Джон Скит ответит на этот вопрос....   -  person MirroredFate    schedule 22.10.2013


Ответы (5)


Хотя классы abstract не могут быть созданы напрямую, модификатор доступа (например, private) в конструкторе имеет значение, когда они наследуются. Делая конструктор private вместо конструктора по умолчанию, вы делаете его таким, что унаследованный класс не может быть создан. Поскольку это единственный конструктор, вы фактически создаете класс sealed, поскольку ни один наследующий класс (если только он не вложен в ICaseConverterContracts) не может быть скомпилирован (по крайней мере, в C#).

Я предполагаю, что код Code Contracts создает экземпляр класса посредством отражения или каким-либо другим способом, который позволяет обойти проблему конструктора, являющегося private.

person Tim S.    schedule 22.10.2013
comment
Это правильно, за исключением части об отражении. Code Contracts не создает экземпляр класса, вместо этого инструменты читают IL напрямую. - person Kris Vandermotten; 22.10.2013

Пометка этого конструктора как частного также предотвращает создание экземпляра любого производного класса:

public class Foo : ICaseConverterContracts
{
    public Foo()  // does not compile as the base class constructor is inaccessible
    {
    }
}

Это явно предотвращает создание экземпляра класса ICaseConverterContracts ни при каких обстоятельствах, поскольку это не тот класс, который вообще следует создавать.

person Dan Puzey    schedule 22.10.2013

ContractClassFor — это фиктивная реализация класса для интерфейса, не имеющая никакой другой цели, кроме публикации контрактов, которые интерфейс требует и обещает своим потребителям. Как видно из соответствующего ContractClass, интерфейс и его контрактный класс тесно связаны. (Эта довольно неуклюжая реализация, по-видимому, связана с тем, что контракты не могут быть опубликованы непосредственно в интерфейсе, поскольку на интерфейсе не разрешена реализация). Контракты в фиктивном ContractClassFor затем применяются для всех real реализаций базового интерфейса (также обратите внимание, что только фиктивная реализация ContractClassFor может публиковать контракты для интерфейса - в противном случае разные реализации могли бы иметь разные контракты, что на самом деле не имело бы смысла .)

Класс ContractClassFor никогда не создается, и вы часто найдете фиктивные реализации только для того, чтобы заставить компилятор компилироваться, например.

public string Convert(string text)
{
     Contract.Requires(text != null);
     Contract.Ensures(Contract.Result<string>() != null);
     return default(string); // returns dummy value
}

or

public string Convert(string text)
{
     Contract.Requires(text != null);
     Contract.Ensures(Contract.Result<string>() != null);
     throw new NotImplementedException();
}

и Т. Д.

person StuartLC    schedule 22.10.2013

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

Частный конструктор предотвращает наследование, поскольку производный класс не может вызывать конструктор базового класса.

Но так как Code Contract Rewriter удаляет все эти классы из вашего кода. ICaseConverterContracts не будет в скомпилированной сборке.

Единственное место, где будет отображаться ваш класс ICaseConverterContracts, — это сборка контрактов в bin/Debug/CodeContracts/MyProject.Contracts.dll. Но эта сборка предназначена только для статического верификатора: вы никогда не будете использовать ее напрямую или даже ссылаться на нее. Таким образом, наличие частного конструктора там также не требуется.

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

person Daniel A.A. Pelsmaeker    schedule 22.10.2013

Наличие частного конструктора означает, что вы не можете наследовать от этого класса.

Поведение аналогично поведению класса sealed, за исключением того, что класс по-прежнему может наследоваться вложенными классами.

person ghembo    schedule 22.10.2013