3_C#_классы_сборка_мусора

advertisement
Структура программы на С#
 Любая программа на С# - это набор классов, структур и
других определенных программистом типов.
 В NET Framework 1.0 – 1.1 в одном файле может быть
определено несколько типов, но каждый тип должен быть
определен в одном файле.
// A.cs
public class A {
// Поля данных
// и методы
}
public struct B {
// Поля данных
// и методы
}
// C.cs
public class C {
// Поля данных
// и методы
}
// M.cs
public class App {
public static void
Main(string[] args)
public enum D {
// Константы
}
{
// Точка входа
}
}
 A.cs + B.cs = lib.dll
 A.cs + M.cs = app.exe
Частичные(partial) классы (.NET Framework 2.0)
 Можно определить тип в разных файлах.
 Если тип определяется в разных файлах, перед каждым
объявлением типа указывается ключевое слово partial.
// файл Abc.cs
public partial class Abc
{
private string key;
public virtual string ToString()
{ return “key=“ + key;
}
}
// файл Abc1.cs
public partial class Abc
{
public string Key
{ get() { return key;}
set() { key = value;}
}
}
Пространства имен
 Позволяют избежать совпадения имен в больших
проектах
namespace A {
class Cls { … }
}
namespace B {
class Cls { … }
}
A.Cls obj1 = new A.Cls();
B.Cls obj2 = new B.Cls();
 Не связаны с
физическим
расположением классов
A
B
c1.dll
c2.dll
Пространства имен -2
 Можно использовать сокращенную запись
using A;
Cls obj = new Cls();
// A.Cls obj = A.Cls();
 Можно вводить псевдонимы
using Btn = System.Windows.Forms.Button;
Btn b = new Btn();
// System.Windows.Forms.Button b =
new System.Windows.Forms.Button();
 Директиву using можно использовать только в начале
файла или пространства имен
Вложенные пространства имен.
 Пространства имен могут быть вложены
namespace A {
namespace B {
class Cls { … }
}
}
namespace A.B {
class Cls { … }
}
 using для объемлющего пространства имен не означает
сокращение имен для вложенных using.
using A;
using A.B;
A.B.Cls obj1;
B.Cls obj2; // Ошибка
A.B.Cls obj1;
Cls obj2; // OK.
Классы и структуры в C#
Класс (class) – ссылочный тип.
Структура (struct) – тип-значение.
Структура не может
• иметь деструктор;
• иметь конструктор без параметров;
• использоваться как базовый тип.
Описание класса в C#
модификатор_доступа class имя-класса {
… описание данных и методов …
}
 Модификаторы доступа для классов:
public
internal
- класс доступен из других сборок;
- класс видим только внутри данной сборки;
 Тип доступа по умолчанию – internal.
Члены класса
• Константы
• Поля (field)
• Конструкторы (в том числе без параметров)
• Деструктор
• Методы (статические и экземплярные)
• Свойства (property)
• Индексаторы (свойства с параметрами)
• События (event)
• Вложенные типы
Доступ к членам класса (полям и методам)
Модификатор
Доступ
public
в любом методе любой сборки
protected
в методах самого класса,
в методах производных классов любой
сборки
internal
в любом методе данной сборки
protected internal в любом методе данной сборки,
в производных классах других сборок
private
только в методах самого класса
Вложенный тип может иметь любой из 5 модификаторов
доступа, но уровень доступа не может быть выше уровня
доступа объемлющего типа.
Константы
 Локальные переменные, поля классов и структур
могут иметь модификатор const.
 Константы инициализируются при объявлении.
 Константы применяются для улучшения читаемости
кода.
public class TextEditor {
const int MaxLines = 10000;
…
}
Поля readonly
 Поля классов и структур могут иметь модификатор readonly. Поле
с модификатором readonly инициализируется в конструкторе и
больше не изменяется.
 readonly поля могут инициализироваться явно
public class TextEditor {
readonly int MaxLines = 10000;
…
}
 readonly поля могут инициализироваться по умолчанию
public class TextEditor {
readonly int MaxLines; // Значение 0
…
}
 readonly поля могут инициализироваться в конструкторе
public class TextEditor {
readonly int MaxLines;
public TextEditor(int maxLines) {
MaxLines = maxLines;
string[] lines = new string[MaxLines];
}
…
}
Статические методы и данные
 Методы и поля данных объявляются как статические с
помощью модификатора static.
 Статические данные совместно используются всеми
экземплярами класса.
 Статический метод вызывается через класс.
 Статический метод может быть вызван еще до создания
экземпляра (instance) класса.
double res = Math.Sin(Math.PI/6);
Console.WriteLine("res= {0}", res);
StringBuilder str = new StringBuilder(res.ToString());
Console.WriteLine(str.Insert(0,"sin(pi/6)="));
Инициализация объектов: конструкторы
 При создании объекта выполняются два действия:
• выделяется память под объект;
• выделенный участок памяти инициализируется.
T t = new
T();
Выделение памяти
Инициализация объекта
 Инициализацию выделенного участка памяти
выполняет специальный метод класса – конструктор.
 В классе может быть определено несколько
конструкторов с различными списками параметров.
Конструктор по умолчанию
 Конструктор без параметров называется конструктором
по умолчанию.
 Если конструктор по умолчанию не задан, компилятор
сам создает конструктор по умолчанию.
 Если в классе определен хотя бы один конструктор,
конструктор по умолчанию не создается.
Когда нужен свой конструктор по умолчанию?
• при инициализации объекта требуется выполнить
действия более сложные, чем простое присваивание
значений полям данных;
• требуется изменить уровень доступа к конструктору.
Singleton pattern
 Иногда требуется ограничить количество экземпляров
объекта одним.
public class Singleton {
private Singleton()
{ // выполняем инициализацию
}
public static Singleton GetInstance()
{
if( instance == null)
instance = new Singleton();
return instance;
}
private static Singleton instance;
}
// Singleton s = new Singleton();
// Ошибка!
Singleton s2 = Singleton.GetInstance(); // OK.
Списки инициализаторов
 Чтобы избежать дублирования кода, можно вызывать
один конструктор из другого при помощи специальной
синтаксической конструкции - списка инициализаторов.
public T(string key)
{
this.key = key;
nobj++;
}
public T(string key, double []dt) : this(key)
{
this.dt = new double[dt.Length] ;
Array.Copy(dt, this.dt, dt.Length);
}
 Список инициализаторов применим только в
конструкторах и не может ссылаться сам на себя
(рекурсивный вызов конструкторов).
Статический конструктор
 При выполнении программы под управлением CLR
классы могут загружаться во время выполнения
программы.
 После загрузки класса, но перед его использованием
всегда выполняется статический конструктор
(конструктор класса), инициализирующий статические
поля данных.
public class T {
static int st;
static T()
{
// Инициализация статических полей
}
…
}
Свойства статического конструктора
 Для статического конструктора гарантируется, что:
• Статический конструктор будет вызван перед
созданием первого экземпляра объекта.
• Статический конструктор будет вызван перед
обращением к любому статическому полю класса или
статическому методу класса.
• Статический конструктор будет вызван не более
одного раза за все время выполнения программы.
 Статический конструктор не может иметь модификатор
доступа.
 Статический конструктор не может принимать
параметры.
 Статический конструктор не может быть вызван явно.
Свойства
 Свойство - пара методов со специальными именами:
• метод get() вызывается при получении значения
свойства;
• метод set() вызывается при задании значения свойства.
 Каждое свойство имеет имя и тип.
 Если определен только метод get , свойство доступно
только для чтения. Если определен только метод set,
свойство доступно только для “записи”.
 Свойство может быть связано с закрытым полем класса.
 Свойства работают немного медленнее прямого доступа к
полю.
Cвойства - 2
public class T {
string key;
double [] dt;
public string Key
{ get { return key; }
set { key = value; }
}
public double Sum
{ get
{ double s = 0;
foreach(double d in dt) s +=d;
return s;
}
}
...
}
T t = new T();
t.Key = “abcd”;
double s = t.Sum;
Зачем нужны свойства?
• При помощи свойств можем выполнять свой код при
изменении объекта или запросе состояния объекта.
• Свойства реализуют принцип инкапсуляции.
• Свойства активно используются визуальными
инструментами разработки.
Индексаторы (свойства с параметрами)
 В С# для определения оператора индексирования [ ]
используется специальный синтаксис.
 Индексатор имеет определенный тип.
 Индексатор может быть перегружен – можно
определить индексаторы с различным числом и типами
параметров.
public
{ get
set
}
public
{ get
set
}
double this[int j]
{ return dt[j]; }
{ dt[j] = value;}
double this[int i, int j]
{return dt[Math.Abs(i-j)];}
{dt[Math.Abs(i-j)] = value;}
Передача параметров размерных типов
struct S {…};
class Class1 {
void F (S p1, ref S p2, out S p3) {...}
...
}
p1
p2 и p3
p2
p3
передается по значению - делается ограниченная
(shallow) копия, изменения не возвращаются в
контекст вызова;
передаются по ссылке, изменения возвращаются
в контекст вызова;
перед вызовом должен быть инициализирован;
может быть не инициализирован, в методе
должно быть присвоено значение.
Передача параметров ссылочных типов
class Class1 {
public static void f1 (double[] darr)
// аналог в C++ double*
{
darr[0] = 555;
double[] df = new double[]{5,15};
darr = df;
}
public static void f2 (ref double[] darr) // аналог в C++ double* &
{
darr[0] = 777;
double[] df = new double[]{7,17};
darr = df;
}
static void Main(string[] args)
{ double[]dm = {1,2,3};
Class1.f1(dm);
foreach(double d in dm) Console.Write(" {0}", d); // 555 2 3
Class1.f2(ref dm);
foreach(double d in dm) Console.Write(" {0}", d); // 7 17
}
}
f1
делается копия ссылки dm, при выходе из f1 значение dm не
изменится, но элементы массива изменятся
f2
при выходе из f2 значение dm изменится
Методы с переменным числом параметров
class Class1
{
...
public static void f (T1 p1, T2 p2, params T[] p)
{...}
}
 Только последний параметр метода может иметь
модификатор params.
 Параметр c модификатором params должен быть
одномерным массвом (любого типа).
 Если при вызове не найден метод с точным совпадением
типов фактических параметров, параметры собираются в
массив и вызывается метод с модификатором params.
Перегрузка операторов для классов и структур
 Возможна перегрузка бинарных операторов:
+ - * % / | & ~ << >>
и унарных операторов
+ - ~ ! ++ - Операторы перегружаются с помощью открытых
статических методов. Один из параметров должен
совпадать с объемлющим типом.
 Операторы (составные операции присваивания)
+= -= *= /= %= >>= <<= &= |= ^=
не могут быть перегружены, но когда перегружены
соответствующие им бинарные операции, последние
используются при вычислении выражений с операторами
+= -= *= /= %= >>= <<= &= |= ^=
Перегрузка операторов. Пример
struct Rational {
int a,b;
public Rational(int aa, int bb) { a = aa; b = bb; }
// бинарный +
public static Rational operator+ (Rational ls, Rational rs)
{ return new Rational (ls.a*rs.b + ls.b*rs.a , ls.b*rs.b); }
// унарный public static Rational operator- (Rational r)
{ return new Rational(-r.a, r.b); } }
static void Main(string[] args)
{
Rational r1 = new Rational(1,2);
Rational r2 = new Rational(1,3);
Rational r3 = r1 + r2;
r2 += r1; // Вычисляется как r2 = r2 + r1;
Rational r4 = -r1;
}
Перегрузка операторов ++ и -Операторы ++ и -- могут применяться в префиксной и постфиксной
форме:
r1 = ++a1;
// Вычисляется как a1 = operator ++ (a1); r1 = a1;
r2 = a2++;
// Вычисляется как r2 = a2; a2 = operator ++ (a2);
struct Rational
{
int a,b;
public Rational(int aa,int bb) { a = aa; b = bb; }
public static Rational operator ++ (Rational r)
{ return new Rational(r.a + r.b, r.b); }
public override string ToString()
{ return "(" + a + "," + b +")"; }
}
static void Main(string[] args)
{ Rational a1 = new Rational(1,2);
Rational a2 = new Rational(1,2);
Rational r1 = ++a1;
Console.WriteLine("a1={0} r1={1}", a1, r1);// a1=(3,2) r1=(3,2)
Rational r2 = a2++;
Console.WriteLine("a2={0} r2={1}", a2, r2);// a2=(3,2) r2=(1,2)
}
Перегрузка операторов сравнения
 Перегрузка операторов сравнения возможна только в парах:
<= и >=
<и>
== и !=
 Чтобы избежать дублирования кода и связанных с ним ошибок,
рекомендуется поместить код для сравнения в единственный
статический метод и вызывать его из методов, перегружающих
операторы.
struct Rational {
int a,b;
…
public static int Compare(Rational ls, Rational rs) {
long d = ls.a*(long)rs.b - rs.a*(long)ls.b;
if(d < 0)
return -1; // Первый аргумент меньше второго
else if(d > 0)
return 1; // Первый аргумент больше второго
else
return 0;
}
public static bool operator < (Rational ls, Rational rs)
{ return (Compare(ls,rs) < 0); }
public static bool operator > (Rational ls, Rational rs)
{ return (Compare(ls,rs) > 0); }
}
Операторы преобразования типов
 Можно объявлять явные и неявные преобразования для структуры
или класса как статические методы.
struct Rational{
Оператор явного преобразования bool->Rational
int a,b;
public static explicit operator Rational ( bool val )
{ int a = val ? 1 : -1;
return new Rational( a,1); }
Оператор неявного преобразования int->Rationall
public static implicit operator Rational ( int val )
{ return new Rational(val,1); }
public static explicit operator bool ( Rational r )
{ return r.a*r.b > 0; }
…
}
static void Main(string[] args)
{
Rational r1 = 5;
bool b1 = true;
Rational r2 = (Rational) b1;
bool b2 = ( bool) r1;
}
Жизненный цикл объекта
 Создание объекта при вызове оператора new
• Под объект выделяется область памяти из управляемой
кучи.
• Область памяти инициализируется при работе
конструктора.
• Возможно захватывается ресурс, например, открывается
файл или устанавливается соединение с базой данных.
 Использование объекта
• Вызываются методы объекта, производится доступ к
полям данных объекта.
 Уничтожение объекта (точное время не определено)
• При помощи деструктора (финализатора)
объект превращается в область памяти.
• Область памяти возвращается системе.
 Освобождением занятых ресурсов занимается сборщик мусора.
Деструктор
 В классе можно определить деструктор.
 В отличие от C++ порядок вызова деструкторов в C# не
определен.
 Перед освобождением памяти сборщик мусора вызывает
деструктор, но возможны ситуации, когда деструктор для
объекта не вызывается совсем.
 Определять свои деструкторы нужно только в случае
крайней необходимости - они создают значительную
нагрузку на систему.
Деструкторы и метод Finalize
 На самом деле наличие в классе деструктора означает
неявное переопределение метода Finalize
class T {
~T() { Console.WriteLine(“T destructed”); }
}
class T {
protected override void Finalize(){
try {
Console.WriteLine(“T destructed”);
}
finally {
base.Finalize();
}
}
}
Сборщик мусора ( Garbage Collector)
CLR использует модель сборки с поколениями
0 – (128-256-512 K)
1 – (2 Mб)
2 – (10 Mб)
 Сборщик мусора начинает работу, когда заполнено
поколение 0, перемещая объекты в поколение 1.
 Для больших объектов (> 85000 байт) своя куча и свой
алгоритм работы GC.
 Алгоритмы сборки отличаются в debug и release версиях
и в различных версиях OC.
Сборщик мусора ( Garbage Collector) - 2
 Перед вызовом Ctor объекты с завершением (с Dtor)
заносятся в специальный список объектов, для которых
надо вызвать Dtor перед освобождением памяти.
 При сборке мусора в поколении 0 объекты с
завершением не уничтожаются, а заносятся в специальную
очередь завершения. Очередь завершения обрабатывает
специальный высокоприоритетный поток.
В каком случае Dtor может быть не выполнен?
 После завершения работы приложения CLR вызывает
Finalize() для каждого объекта с завершением, но если
время возврата из него более 2 сек, CLR завершает
процесс и остальные методы Finalize() не вызываются.
Download