Класс это тип, который вы определяете: он объединяет данные (поля) с кодом, который с этими данными работает (методами). Объект это один экземпляр класса, созданный во время выполнения через new. Один класс, много объектов, у каждого свои значения.
Объявление класса и создание объектов
Объявление класса перечисляет его члены в фигурных скобках. Поля хранят данные, методы хранят поведение. new ClassName() выделяет объект и даёт вам ссылку на него.
Вывод:
Maya: 120.50
Omar: 40
Withdraw 200 succeeded: False
У каждого объекта свои Owner и Balance. Внутри метода Balance означает «баланс объекта, у которого вызван этот метод», поэтому maya.Deposit и omar.Deposit меняют разные числа. Этот неявный объект доступен как this, когда его нужно назвать.
Поля, которым вы не присвоили значение, начинают со значения по умолчанию: 0 для чисел, false для bool и null для строк и других классов.
Члены здесь public, чтобы до них мог добраться Main. Настоящие классы обычно держат поля private и открывают вместо них свойства; правила описаны на странице о модификаторах доступа. Класс также может объявить конструктор, чтобы задавать поля при создании объекта, это описано на странице о конструкторах.
Класс это ссылочный тип
Переменная типа-класса не содержит объект. Она содержит ссылку на объект, который живёт в куче. Присваивание переменной копирует ссылку, поэтому две переменные могут указывать на один и тот же объект.
Вывод:
5
6
True
False
Здесь проявляются три следствия:
- Изменение объекта через
secondвидно черезfirst, потому что объект только один. - Передача объекта в метод передаёт ссылку. Метод может изменить поля объекта, и вызывающий код это увидит. (Присваивание нового объекта параметру внутри метода не затрагивает переменную вызывающего кода; для этого нужен
ref.) ==для двух экземпляров класса по умолчанию сравнивает ссылки: «это тот же объект?», а не «у них одинаковые значения?». Уlookalikeте же данные, но это другой объект.
Если нужно копирование при присваивании и сравнение по значению, посмотрите на структуры, которые являются типами значений, или на записи, которые сравниваются по значению.
null и NullReferenceException
Ссылочная переменная может содержать null, то есть «нет объекта». Обращение к члену через null выбрасывает NullReferenceException, самое частое исключение времени выполнения в коде на C#.
Вывод:
True
no city on file
NullReferenceException: Address was never set
Lisbon
Исправление почти никогда не состоит в перехвате исключения. Выясните, почему ссылка равна null, и либо создайте объект раньше, либо проверяйте перед использованием, либо используйте null-условный оператор ?., который останавливается и даёт null вместо исключения. Затем ?? подставляет запасное значение. Оба оператора описаны в разделе типы, допускающие null.
Переопределение ToString
Каждый класс наследуется от object, который предоставляет ToString(). Унаследованная версия возвращает полное имя типа (включая пространство имён, если оно есть), а это редко то, что хочется напечатать. Переопределите его:
Вывод:
Plain
Mug (8.50)
In cart: Mug (8.50)
Total line: Mug (8.50)
Console.WriteLine, интерполяция строк и + со строкой вызывают ToString() за вас. Отладчик тоже его показывает, поэтому переопределение полезно, даже если вы сами никогда не печатаете объект.
Инициализаторы объектов
Инициализатор объекта задаёт открытые поля и свойства сразу после создания объекта, в том же выражении. Он читается лучше, чем список присваиваний, и хорошо работает внутри литералов коллекций.
Вывод:
A12: 25 (0 extras)
A13: 40 (2 extras)
Две детали во втором билете. Price = 40m переопределяет 25m из инициализатора поля, потому что инициализатор объекта выполняется после построения объекта. У Extras = { "Parking", "Poster" } нет new: он вызывает Add у списка, который уже хранится в поле, а не заменяет его.
Инициализатор объекта не является конструктором. Он может задавать только члены, доступные вызывающему коду, и не может потребовать, чтобы член вообще был задан. Когда объект без определённых значений некорректен, требуйте их в конструкторе.
Разделяемые классы (partial)
Ключевое слово partial разбивает один класс на несколько файлов. Компилятор объединяет части в один класс.
// Invoice.cs
public partial class Invoice
{
public decimal Subtotal;
public decimal Total() => Subtotal + Tax();
}
// Invoice.Tax.cs
public partial class Invoice
{
private decimal Tax() => Subtotal * 0.2m;
}
Каждая часть должна содержать partial и находиться в том же пространстве имён и том же проекте. Обычное применение это сгенерированный код: дизайнеры WinForms, генераторы исходного кода и некоторые ORM пишут одну часть и оставляют другую вам, поэтому повторная генерация никогда не перезаписывает ваш код. Разбиение написанного вручную класса по файлам только ради того, чтобы он стал короче, обычно скрывает класс, который должен быть двумя классами.
Частые ошибки
- Использование объекта до его создания. Поле типа-класса равно
null, пока вы не присвоите емуnew .... Первое обращение к члену выбрасываетNullReferenceException. - Ожидание, что присваивание копирует.
var b = a;для класса даёт второе имя тому же объекту. Когда нужен независимый объект, создайте новый и скопируйте значения. - Сравнение объектов через
==в расчёте на сравнение значений. Для классов оно сравнивает ссылки, если класс не перегружает==(как это делаетstring). Сравнивайте нужные поля или переопределитеEquals. - Вывод объекта, дающий имя его типа. Переопределите
ToString(). - Всё объявлено
public. В маленьких примерах это работает, но позволяет любому коду привести объект в некорректное состояние, например к отрицательному балансу. Держите поля закрытыми и меняйте их через методы, проверяющие правила.
Часто задаваемые вопросы
Что такое класс в C#?
Класс это тип, который вы определяете: именованная группа данных (полей и свойств) и поведения (методов). class Account { public decimal Balance; public void Deposit(decimal amount) { Balance += amount; } } объявляет такой класс. Класс это чертёж; он ничего не делает, пока вы не создадите из него объекты через new.
Чем класс отличается от объекта в C#?
Класс это определение, написанное в исходном коде один раз. Объект это один его экземпляр, созданный во время выполнения через new, с собственной копией каждого поля экземпляра. Двойной вызов new Account() даёт два объекта одного класса с независимыми балансами.
Как создать объект в C#?
Используйте new, затем имя класса и аргументы конструктора: var account = new Account();. В том же выражении можно задать открытые поля и свойства через инициализатор объекта: new Account { Owner = "Maya" }. Переменная хранит ссылку на новый объект, а не сам объект.
Почему возникает NullReferenceException в C#?
Вы обратились к члену (.Name, .Length, вызову метода) через переменную, содержащую null, поэтому объекта, с которым можно работать, нет. Частые причины: поле, которому так и не присвоили значение (поля-классы изначально равны null), поиск, вернувший null, или метод, вернувший null при неудаче. Проверяйте через if (x != null), используйте x?.Name или убедитесь, что объект создан до использования.
Как переопределить ToString в C#?
Объявите в классе public override string ToString() и верните нужный текст, например public override string ToString() => $"{Name} ({Price:F2})";. Без этого Console.WriteLine(obj) и интерполяция строк печатают имя класса, потому что именно его возвращает object.ToString().