Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ПИ Программирование на С# _Хотов.docx
Скачиваний:
4
Добавлен:
01.07.2025
Размер:
2.22 Mб
Скачать
        1. Оператор fixed и закрепление указателей

Ранее мы посмотрели, как создавать указатели на типы значений, например, int или структуры. Однако кроме структур в C# есть еще и классы, которые в отличие от типов значений, помещают все связанные значения в куче. И в работу данных классов может в любой момент вмешаться сборщик мусора, периодически очищающий кучу. Чтобы фиксировать на все время работы указатели на объекты классов используется оператор fixed.

Допустим, у нас есть класс Person:

public class Person

{

public int age;

public int height;

}

Зафиксируем указатель с помощью оператора fixed:

unsafe

{

Person person = new Person();

person.age = 28;

person.height = 178;

// блок фиксации указателя

fixed(int* p = &person.age)

{

if (*p < 30)

{

*p = 30;

}

}

Console.WriteLine(person.age); // 30

}

Оператор fixed создает блок, в котором фиксируется указатель на поле объекта person. После завершения блока fixed закрепление с переменных снимается, и они могут быть подвержены сборке мусора.

Кроме адреса переменной можно также инициализировать указатель, используя массив, строку или буфер фиксированного размера:

unsafe

{

int[] nums = { 0, 1, 2, 3, 7, 88 };

string str = "Привет мир";

fixed(int* p = nums)

{

}

fixed(char* p = str)

{

}

}

При инициализации указателей на строку следует учитывать, что указатель должен иметь тип char*.

    1. Dynamic Language Runtime

      1. Dlr в c#. Ключевое слово dynamic

Хотя C# относится к статически типизированным языкам, в последних версиях языка были добавлены некоторые динамические возможности. Так, начиная с .NET 4.0 была добавлена новая функциональность под названием DLR (Dynamic Language Runtime). DLR представляет среду выполнения динамических языков, например, таких языков как IronPython и IronRuby.

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

Благодаря этой среде DLR C# может создавать динамические объекты, члены которых выявляются на этапе выполнения программы, и использовать их вместе с традиционными объектами со статической типизацией.

Ключевым моментом использования DLR в C# является применение типов dynamic. Это ключевое слово позволяет опустить проверку типов во время компиляции. Кроме того, объекты, объявленные как dynamic, могут в течение работы программы менять свой тип. Например:

class Program

{

static void Main(string[] args)

{

dynamic x = 3; // здесь x - целочисленное int

Console.WriteLine(x);

x = "Привет мир"; // x - строка

Console.WriteLine(x);

x = new Person() { Name = "Tom", Age = 23 }; // x - объект Person

Console.WriteLine(x);

Console.ReadLine();

}

}

class Person

{

public string Name {get;set;}

public int Age { get; set; }

public override string ToString()

{

return Name + ", " + Age.ToString();

}

}

Несмотря на то, что переменная x меняет тип своего значения несколько раз, данный код будет нормально работать. В этом использование типов dynamic отличается от применения ключевого слова var. Для переменной, объявленной с помощью ключевого слова var, тип выводится во время компиляции и затем во время выполнения больше не меняется.

Также можно найти общее между использованием dynamic и типом object. Если в предыдущем примере мы заменим dynamic на object: object x = 3;, то результат будет тот же. Однако и тут есть различия. Например:

object obj = 24;

dynamic dyn = 24;

obj += 4; // так нельзя

dyn += 4; // а так можно

На строке obj += 4; мы увидим ошибку, так как операция += не может быть применена к типам object и int. С переменной, объявленной как dynamic, это пройдет, так как ее тип будет известен только во время выполнения.

Еще одна отличительная особенность использования dynamic состоит в том, что это ключевое слово применяется не только к переменным, но и к свойствам и методам. Например:

class Person

{

public string Name {get;set;}

public dynamic Age { get; set; }

// выводим зарплату в зависимости от переданного формата

public dynamic getSalary(dynamic value, string format)

{

if (format=="string")

{

return value + " рублей";

}

else if (format == "int")

{

return value;

}

else

{

return 0.0;

}

}

public override string ToString()

{

return Name + ", " + Age.ToString();

}

}

В классе Person определено динамическое свойство Age, поэтому при задании значения этому свойству мы можем написать иperson.Age=33, и person.Age="тридцать три". Оба варианта будут правильными.

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

dynamic person1 = new Person() { Name = "Том", Age = 27 };

Console.WriteLine(person1);

Console.WriteLine(person1.getSalary(28.09, "int"));

dynamic person2 = new Person() { Name = "Билл", Age = "Двадцать два года" };

Console.WriteLine(person2);

Console.WriteLine(person2.getSalary(30, "string"));

      1. DynamicObject и ExpandoObject

Интересные возможности при разработке в C# и .NET с использованием DLR предоставляет пространство именSystem.Dynamic и в частности класс ExpandoObject. Он позволяет создавать динамические объекты, наподобие тех, то используются в javascript:

dynamic viewbag = new System.Dynamic.ExpandoObject();

viewbag.Name = "Tom";

viewbag.Age = 46;

viewbag.Languages = new List<string> {"english", "german", "french" };

Console.WriteLine("{0} - {1}", viewbag.Name, viewbag.Age);

foreach (var lang in viewbag.Languages)

Console.WriteLine(lang);

// объявляем метод

viewbag.IncrementAge = (Action<int>)(x => viewbag.Age += x);

viewbag.IncrementAge(6); // увеличиваем возраст на 6 лет

Console.WriteLine("{0} - {1}", viewbag.Name, viewbag.Age);

Консольный вывод:

Tom - 46

english

german

french

Tom - 52

У динамического объекта ExpandoObject можно объявить любые свойства, например, Name, Age, Languages, которые могут представлять самые различные объекты. Кроме того, можно задать методы с помощью делегатов.

        1. DynamicObject

На ExpandoObject по своему действию похож другой класс - DynamicObject. Он также позволяет задавать динамические объекты. Только в данном случае нам надо создать свой класс, унаследовав его от DynamicObject и реализовав его методы:

  • TryBinaryOperation(): выполняет бинарную операцию между двумя объектами. Эквивалентно стандартным бинарным операциям, например, сложению x + y)

  • TryConvert(): выполняет преобразование к определенному типу. Эквивалентно базовому преобразованию в C#, например, (SomeType) obj

  • TryCreateInstance(): создает экземпляр объекта

  • TryDeleteIndex(): удаляет индексатор

  • TryDeleteMember(): удаляет свойство или метод

  • TryGetIndex(): получает элемент по индексу через индексатор. В C# может быть эквивалентно следующему выражениюint x = collection[i]

  • TryGetMember(): получаем значение свойства. Эквивалентно обращению к свойству, например, string n = person.Name

  • TryInvoke(): вызов объекта в качестве делегата

  • TryInvokeMember(): вызов метода

  • TrySetIndex(): устанавливает элемент по индексу через индексатор. В C# может быть эквивалентно следующему выражению collection[i] = x;

  • TrySetMember(): устанавливает свойство. Эквивалентно присвоению свойству значения person.Name = "Tom"

  • TryUnaryOperation(): выполняет унарную операцию подобно унарным операциям в C#: x++

Каждый из этих методов имеет одну и ту же модель определения: все они возвращают логическое значение, показывающее, удачно ли прошла операция. В качестве первого параметра все они принимают объект связывателя или binder. Если метод представляет вызов индексатора или метода объекта, которые могут принимать параметры, то в качестве второго параметра используется массив object[] - он хранит переданные в метод или индексатор аргументы.

Почти все операции, кроме установки и удаления свойств и индексаторов, возвращают определенное значение (например, если мы получаем значение свойства). В этом случае применяется третий параметр out object vaue, который предназначен для хранения возвращаемого объекта.

Например, определение метода TryInvokeMember():

public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

Параметр InvokeMemberBinder binder является связывателем - получает свойства и методы объекта, object[] args хранит передаваемые аргументы, out object result предназначен для хранения выходного результата.

Рассмотрим на примере. Создадим класс динамического объекта:

class PersonObject : DynamicObject

{

Dictionary<string, object> members = new Dictionary<string, object>();

// установка свойства

public override bool TrySetMember(SetMemberBinder binder, object value)

{

members[binder.Name] = value;

return true;

}

// получение свойства

public override bool TryGetMember(GetMemberBinder binder, out object result)

{

result = null;

if (members.ContainsKey(binder.Name))

{

result = members[binder.Name];

return true;

}

return false;

}

// вызов метода

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

{

dynamic method = members[binder.Name];

result = method((int)args[0]);

return result != null;

}

}

Класс наследуется от DynamicObject, так как непосредственно создавать объекты DynamicObject мы не можем. И также здесь переопределяется три унаследованных метода.

Для хранения всех членов класса, как свойств, так и методов, определен словарь Dictionary<string, object> members. Ключами здесь являются названия свойств и методов, а значениями - значения этих свойств.

В методе TrySetMember() производится установка свойства:

bool TrySetMember(SetMemberBinder binder, object value)

Параметр binder хранит названием устанавливаемого свойства (binder.Name), а value - значение, которое ему надо установить.

Для получения значения свойства переопределен метод TryGetMember:

bool TryGetMember(GetMemberBinder binder, out object result)

Опять же binder содержит название свойства, а параметр result будет содержать значение получаемого свойства.

Для вызова методов определен метод TryInvokeMember:

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

{

dynamic method = members[binder.Name];

result = method((int)args[0]);

return result != null;

}

Сначала с помощью bindera получаем метод и затем передаем ему аргумент args[0], предварительно приведя его к типу int, и результат метода устанавливаем в параметре result. То есть в данном случае подразумевается, что метод будет принимать один параметр типа int и возвращать какой-то результат.

Теперь применим класс в программе:

static void Main(string[] args)

{

dynamic person = new PersonObject();

person.Name = "Tom";

person.Age = 23;

Func<int, int> Incr = delegate (int x) { person.Age+=x; return person.Age; };

person.IncrementAge = Incr;

Console.WriteLine("{0} - {1}", person.Name, person.Age); // Tom - 23

person.IncrementAge(4);

Console.WriteLine("{0} - {1}", person.Name, person.Age); // Tom - 27

Console.Read();

}

Выражение person.Name = "Tom" будет вызывать метод TrySetMember, в который в качестве второго параметра будет передаваться строка "Tom".

Выражение return person.Age; вызывает метод TryGetMember.

Также у объекта person определен метод IncrementAge, который представляет действия анонимного делегата delegate (int x) { person.Age+=x; return person.Age; }. Делегат принимает число x, увеличивает на это число свойство Age и возвращает новое значение person.Age. И при вызове этого метода будет происходить обращение к методу TryInvokeMember. И, таким образом, произойдет приращение значения свойства person.Age.