Страницы

Поиск по вопросам

Показаны сообщения с ярлыком c#-faq. Показать все сообщения
Показаны сообщения с ярлыком c#-faq. Показать все сообщения

воскресенье, 29 марта 2020 г.

TryOpenExisting() и использование Discards (пустых переменных) на обьектах которые наследуют IDisposable

#c_sharp #c_sharp_faq #mutex #c_sharp_70


Мне нужно только результат функции, который записывается в переменную existing. 

Boolean existing = Mutex.TryOpenExisting(name: key, rights: MutexRights.ReadPermissions,
result: out Mutex _);


Могу ли я использовать "Пустую переменную" в этом коде? Вызовет ли она Dispose();
для этого обьекта автоматически?

Или правильным было бы обьявить переменную Mutex mutex и после вызова метода вызвать
mutex.Dispose(); ?

P.S. Кому не понятна суть вопроса, подробнее про пустые переменные тут: https://docs.microsoft.com/en-us/dotnet/csharp/discards
    


Ответы

Ответ 1



Сама документация описывает что "пустые переменные" просто ссылаются на некую "пустую" область памяти. То есть не занимают места. Что вряд ли означает что они будут вызывать Dispose() у IDisposable обьектов. Я провел личное расследование даного вопроса и написал прогу для тестирования. Простая винформс апликуха на старте которого вызывается метод: public bool TestMethod(out Image bmp) { Thread.Sleep(3000); bmp = Bitmap.FromFile(@"C:\Users\UKS\Desktop\2000x2000pixels.bmp"); Thread.Sleep(1000); return true; } Выкладываю результаты: public Form1() { InitializeComponent(); Image img; var a = TestMethod(out img); img.Dispose(); } Как видно из картинки - Память возросла и освободилась т.к. мы использовали диспоуз Теперь настал черед теста: public Form1() { InitializeComponent(); var a = TestMethod(out _); } Как видим из картинки - память НЕ освободилась когда я использовал Discard-переменную. Из чего делаем вывод что Дискарды не диспоузят данные. Теперь настал черед еще пары тестов: Конструкция: var a = TestMethod(out _.Dispose()); Не сработает. Говорит что _ не существует в даном контексте. Конструкция: var a = TestMethod(out _); _.Dispose(); Даст ровно тот же результат. Вывод: пустые переменные использовать на IDisposable обьектах категорически нельзя. Итак, самый коректный код в даном конкретном случае есть: Mutex mutex; Boolean existing = Mutex.TryOpenExisting(name: key, rights: MutexRights.ReadPermissions, result: mutex); mutex?.Dispose();//проверяем на null. Если не налл, то вызываем диспоуз.

понедельник, 10 февраля 2020 г.

Разница get; set;

#c_sharp #c_sharp_faq


Не совсем понял разницу между  

public object Variable1 {get; set; } 


и  

public object Variable1
{
    get { return this.Variable1; }  
    set {this.Variable1 = value; }   
}


В обоих случаях это свойство. Отличается ли не обработанные геттер и сеттер от обработанных
таким образом?
    


Ответы

Ответ 1



В том виде, который привели вы, разница в том, что первый пример кода корректный, а второй - нет :) У вас во втором случае чтение свойства возвращает это же свойство, что приводит к возврату этого же свойства - и так до бесконечности. Как заметили в комментариях, это бесконечная рекурсия. Вероятно, вы имели в виду вот это: private object field1; public object Variable1 { get { return field1; } set { field1 = value; } } Вот в этом случае разницы нет, public object Variable1 {get; set; } - это автоматически реализуемое свойство, по смыслу - абсолютно тоже самое.

среда, 29 января 2020 г.

Установка анонимного метода обработчиком события в цикле foreach

#c_sharp #c_sharp_faq


Определяю класс-издатель события

class Car
{
    public string Name { get; set; }
    public Car(string name)
    {
        Name = name;
    }
    public event EventHandler Started;
    public void Start()
    {
        if (Started != null)
            Started(this, EventArgs.Empty);
    }
}


, класс-подписчик

class Driver
{
    public string Name { get; set; }
    public Driver(string name)
    {
        Name = name;
    }
}


, тестирую

static void Main(string[] args)
{
    var fomenko = new Driver("Фоменко");
    var shumaher = new Driver("Шумахер");
    var vasya = new Driver("Вася");
    Driver[] drivers = new Driver[]
        {
            fomenko, shumaher, vasya
        };
    List cars = new List();
    foreach (var driver in drivers)
    {
        var car = new Car
            (
                driver == fomenko ?
                "Маруся"
                : driver == shumaher ?
                "Ф1"
                : "Запорожец"
            );                
        car.Started += delegate(object o, EventArgs ea)
            {
                Console.WriteLine("Стартовала машина {0} с пилотом {1}",
                    car.Name, driver.Name);
            };
        cars.Add(car);
    }

    foreach (var car in cars)
    {
        car.Start();
    }
    Console.ReadKey();
}


Выдаёт

Стартовала машина Маруся с пилотом Вася
Стартовала машина Ф1 с пилотом Вася
Стартовала машина Запорожец с пилотом Вася


А хотелось бы

Стартовала машина Маруся с пилотом Фоменко
Стартовала машина Ф1 с пилотом Шумахер
Стартовала машина Запорожец с пилотом Вася


Почему так происходит? Как исправить?



Версия C# 3.0 .Net 3.5
    


Ответы

Ответ 1



Это - особенность старой версии языка. Переменная цикла захватывается по ссылке, а не по значению - и потому к моменту возникновения события указывает на последнего водителя. Решений тут три: Если возможно, перейдите на современную версию языка. Это проще, чем вы думаете: Visual Studio Community Edition бесплатна для любого личного использования, обучения или работы над открытыми проектами. Скопируйте значение переменной цикла в локальную переменную: foreach (var driver in ...) { var driver2 = driver; //... } Смените тип коллекции drivers с массива на список (List) - тогда вы получите метод ForEach, принимающий делегат: drivers.ForEach(driver => { //... });

Ответ 2



Небольшое уточнение/дополнение к правильному ответу @Pavel Mayorov. В C# до 5-ой версии цикл foreach (var driver in drivers) { // тело цикла } раскрывался примерно в такую конструкцию: using (var en = drivers.GetEnumerator()) { Driver driver; // вне цикла while (en.MoveNext()) { driver = en.Current; // тело цикла } } Поэтому все замыкания видели одну и ту же переменную driver, которая менялась с каждой итерацией цикла. И значит, при приходе события Started у этой переменной было уже «финальное» значение. Начиная с версии 5.0, цикла стал раскрываться в другую конструкцию: using (var en = drivers.GetEnumerator()) { while (en.MoveNext()) { Driver driver = en.Current; // внутри цикла // тело цикла } } И значит, каждое замыкание теперь видит свою переменную driver, только для этой итерации. Таким образом, в C# 5+ ваш код будет вести себя ожидаемым образом. Дополнительное чтение по теме: Closing over the loop variable considered harmful.

Ответ 3



Ваш delegate захватывает переменную driver (это именно closure), а когда машины стартуют, то в ней находится последний в списке водитель, он же Вася. Как уже посоветовали, попробуйте "внутри форича скопипастить водителя в переменную" (лучше прямо driver.Name) и посмотрите на результат. Правильнее всего, конечно, в Car иметь ссылку на Driver

воскресенье, 12 января 2020 г.

Хешкод, переопределение метода GetHashCode

#c_sharp #c_sharp_faq #hashcode


Господа, не могу понять каким образом переопределять метод GetHashCode(). Ведь, насколько
я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Тогда
как мне его переопределить ?? Если не затруднит, то хотелось бы увидеть какой-то элементарный
пример.
И еще не пойму, почему разные хешкоды в коде
using System;
class a
{
    public int x;
    public a(int y)
    {
        x = y;
    }
}
class b
{
    static void Main()
    {
        Console.WriteLine(new a(5).GetHashCode() + " " + new a(5).GetHashCode());
    }
}

Ведь тут написано https://msdn.microsoft.com/ru-ru/library/system.object.gethashcode(v=vs.110).aspx


Для двух одинаковых объектов
возвращенные хэш-коды равны
    


Ответы

Ответ 1



Ведь, насколько я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Так ведь и метод вы переопределяете в своем же классе :). Что-то типа: class a { public int x; public a(int y) { x = y; } public override int GetHashCode() { return x; } } Есть несколько правил для переопределения GetHashCode(), основные: 1) Используемая функция должна давать хорошее распределение. Это, строго говоря, зависит от данных, однако часто хорошо подходит подобная функция: public override int GetHashCode() { int hashcode = field1.GetHashCode(); hashcode = 31 * hashcode + field2.GetHashCode(); hashcode = 31 * hashcode + field3.GetHashCode(); // и т.д. для остальный полей return hashcode; } 2) Эта функция должна быть быстрой. 3) GetHashCode() не должен выбрасывать исключения. 4) В идеале GetHashCode() не должен меняться в течение жизни объекта, т.е. полагаться только на неизменяемые члены класса. На практике этим часто пренебрегают, пока не стрельнет. Так же не забудьте, что Equals() и GetHashCode() всегда должны идти в паре: переопределили один метод, переопределяйте и другой. И если два объекта равны, то у них должен быть одинаковый хэшкод. Обратное необязательно верно (хэш-функция может вернуть одинаковое дначение для разных объектов). Что почитать (на английском): Про правильное переопределение GetHashCode() Про хэш-функции

четверг, 9 января 2020 г.

В чем принципиальная разница между обобщенными методами и обобщенными типами?

#c_sharp #generics #интерфейс #c_sharp_faq


Столкнулся с непониманием.
Вот предположим, мне нужно создать интерфейс для какого то элемента бизнес логики,
но я совершенно ничего не хочу знать о DTO между BLL и уровнем представления, для этого
я напишу что то в таком духе:

public interface IOrderService where T : class
{
    int MakeOrder(T t);
    T GetOrder(int id);
    IEnumerable GetOrders();
    void Dispose();
}


тогда реализация на уровне BLL будет какая то такая:

class OrderService : IOrderService
{
    public void Dispose()
    {
        throw new NotImplementedException();
    }

    public OrderDTO GetOrder(int id)
    {
        throw new NotImplementedException();
    }

    public IEnumerable GetOrders()
    {
        throw new NotImplementedException();
    }

    public int MakeOrder(OrderDTO t)
    {
        throw new NotImplementedException();
    }
}


Но почему я не могу сделать интерфейс таким:

public interface IResourceService
{
    int MakeResource(T t);
    T GetResource(int id);
    IEnumerable GetResources();
    void Dispose();
}


и реализовать его как то так:

class ResourceService : IResourceService
{
    public void Dispose()
    {
        throw new NotImplementedException();
    }

    public ResourceDTO GetResource(int id)
    {
        throw new NotImplementedException();
    }

    public IEnumerable GetResources()
    {
        throw new NotImplementedException();
    }

    public int MakeResource(ResourceDTO t)
    {
        throw new NotImplementedException();
    }
}


Я понимаю, что речь идет о каком то непонимании базовых принципов. Поэтому прошу
помочь разобраться.

Дополнение:

В общем вопрос свелся к тому, можно ли сделать как то так?

public interface IResourceService
{
    T GetResource(int id);
}

public class ResourceService : IResourceService
{
    public T GetResource(int id) where T : ResourceDTO
    {
        return new ResourceDTO();
    }
}


Если да, то как, если нет, то почему?
    


Ответы

Ответ 1



Но почему я не могу сделать интерфейс таким: Можешь, однако в этом случае T для каждой из функций int MakeResource(T t); T GetResource(int id); IEnumerable GetResources(); будет своим и не зависеть от других, то есть можно будет вызвать MakeResource с одним T, GetResource(id) с другим T, GetResource() с третьим T В случае же с public interface IOrderService where T : class { int MakeOrder(T t); T GetOrder(int id); IEnumerable GetOrders(); void Dispose(); } Тип T будет один и тот же у каждой из функций, тот что указан у класса. т.е. получается при реализации интерфейса IResourceService (ResourceService) я не смогу ограничить методы по типу Да, ограничения на параметр типа должны совпадать с ограничениями типа в интерфейсе. То есть, если было , то оно и останется, и указать параметр типа можно будет только при вызове.

Ответ 2



В общем вопрос свелся к тому, можно ли сделать как то так? public interface IResourceDTO { ... } public interface IResourceService { T GetResource(int id) where T : class, IResourceDTO, new(); } public class ResourceService : IResourceService { public T GetResource(int id) where T : class, IResourceDTO, new() { return new T(); } }

понедельник, 30 декабря 2019 г.

Обобщенные типы в С#

#c_sharp #generics #c_sharp_faq


public class Example
{
    public static void Main()
    {
        Myclass ob = new Myclass();
    }


    class Myclass where T: new()
    {
        public T instanse = new T();
    }
    class Testclass { }
} 


Вопрос: Myclass - это тип. Testclass - тоже тип. Какого же тогда типа экземпляр ob?
    


Ответы

Ответ 1



MyClass — это как бы не совсем тип. Это обобощённый тип (на английском — generic). Вы можете сконструировать экземпляр обобщённого типа только указав типы-аргументы, которые заменят формальный параметр-тип T. Экземпляр обобщённого типа самого по себе сконструировать невозможно. Для каждого обобщённого типа существует (обычно бесконечно много) конкретизаций: конкретных типов, которые соответствуют определённым значениям типов-параметров. Соответственно, тип ob и есть такая конкретизация: Myclass с параметром T = Testclass. Такая конкретизация в языке C# записывается как Myclass.

Ответ 2



Неточность заключается в том, что в приведенном коде нет типа/класса Myclass Есть Myclass - класс с generic-параметром. При создании указывается конкретный T: Myclass и переменная получает этот тип: Myclass

четверг, 19 декабря 2019 г.

Чем отличается хранение в памяти массивов из величин значимого и ссылочного типа?

#c_sharp #массивы #память #типы #c_sharp_faq


Чем отличается хранение в памяти массивов из величин значимого и ссылочного типа?
    


Ответы

Ответ 1



Массив - это непрерывная область памяти, в которой последовательно, друг за другом, размещены некоторые элементы. У массива есть заголовок, о котором, обычно, никто не говорит (и не знает), и собственно значения. Если речь о значимых типах (структурах), они размещаются непосредственно в области выделенной для массива. В случае ссылочных типов (классов), их экземпляры размещаются в куче, а массив содержит всё те же структуры (IntPtr) - указатели на эти объекты. В отличии от массива значимых типов, массив ссылочных типом может содержать элементы указывающие на один и тот же объект.

Ответ 2



Если отвлечься от специфики именно массивов C# (которые являются ссылочными), то переменная значимого типа хранит непосредственно значение, а переменная ссылочного типа хранит адрес значения. Само значение при этом хранится в динамической памяти (куче).

Ответ 3



Все массивы являются ссылочными типами. Arrays are mechanisms that allow you to treat several items as a single collection. The Microsoft® .NET Common Language Runtime (CLR) supports single-dimensional arrays, multidimensional arrays, and jagged arrays (arrays of arrays). All array types are implicitly derived from System.Array, which itself is derived from System.Object. This means that all arrays are always reference types which are allocated on the managed heap, and your app's variable contains a reference to the array and not the array itself. https://stackoverflow.com/questions/1533757/is-int-a-reference-type-or-a-value-type https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/bb985948(v=msdn.10)

суббота, 14 декабря 2019 г.

В чем суть отличия между ссылочными и значимыми типами данных в C#?

#c_sharp #c_sharp_faq


В чем суть отличия между ссылочными и значимыми типами данных в C#?    


Ответы

Ответ 1



Значимые типы хранят значение, а ссылочные - ссылку на значение. class ByRef { public byte Value { get; set; } } struct ByVal { public byte Value { get; set; } } class Program { static void Main(string[] args) { ByRef byRef = new ByRef { Value = 0 }; ByVal byVal = new ByVal { Value = 0 }; } } В этом коде и byRef, и byVal создаются как локальные переменные метода Main на стеке. Переменная byVal содержит значение Value, а переменная byRef содержит ссылку на значение Value, хранящееся на куче.

Ответ 2



Основное различие между ссылочными и значимыми типами данных — смысл равенства и копирования. Для значимых типов при копировании вы получаете новый экземпляр, содержащий копии значений исходного экземпляра. Иными словами, экземпляры ведут себя как значения, например, как числа. Если вы скопировали значение, и изменили исходное значение, то эти изменения никак не отразятся на копии: Point p1 = new Point(1, 2); Point p2 = p1; // (1, 2) p1.X = 100; // p2.X всё ещё равно 1 Для ссылочных типов, копирование даёт вам новую ссылку на те же данные. Соответственно, если вы меняете объект ссылочного типа, то эти изменения становятся доступны по любой из ссылок на объект: XElement e1 = new XElement("test") { Value = "hello" }; XElement e2 = e1; e1.Value = "goodbye"; // e2.Value стало равно "goodbye" Теперь, равенство. Одинаковые экземпляры типов-значений, не зависимо от того, являются ли они копиями одного общего объекта или нет, являются равными. Экземпляры ссылочных типов, даже если они содержат одинаковые данные, являются разными. Point p1 = new Point(1, 2), p2 = new Point(1, 2); // p1 == p2 XElement e1 = new XElement("test") { Value = "hello" }, e2 = new XElement("test") { Value = "hello" }; // e1 != e2 Можно сказать, что объекты ссылочных типов обладают индивидуальностью, а объекты значимых типов — нет. Разумеется, семантику сравнения можно кастомизировать, перегрузив оператор сравнения (и/или реализовав интерфейс IEquatable). При этом можно, например, заставить ссылочный тип вести себя так, как будто бы он является значимым типом. И наоборот, храня данные значимого типа в неизменяемом поле ссылочного типа, можно заставить значимый тип вести себя как ссылочный. Все остальные отличия являются лишь следствиями этих, главных отличий. То, что переменные ссылочного типа в Майкрософтовской реализации хранят внутри ссылку, есть по сути деталь имплементации. То, что экземпляры значимых типов иногда попадают на стек (а именно, когда они не являются полями классов, и не лежат в замыкании, async-методе или методе-генераторе), также является деталью имплементации (и может быть не так в будущих версиях или другой имплементации платформы .NET). Если вы выбираете, использовать для вашего объекта структуру (значимый тип) или класс (ссылочный тип), задайте себе вопрос: являются ли два объекта с одинаковыми данными одним и тем же, или разными? Если это, к примеру, объекты, представляющие человека, и в данных у вас его имя, фамилия и возраст, то имеет смысл использовать класс: ведь два человека с одинаковыми именами всё ещё не являются одним и тем же человеком. А если это, например, объект, представляющий рациональную дробь, то нету смысла разделять различные экземпляры дроби 3/8, так что тут уместно воспользоваться структурой.

Ответ 3



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

понедельник, 9 декабря 2019 г.

Команда using()

#c_sharp #c_sharp_faq


using(var variable)
{

}


Правильно ли я понял, что данная конструкция создает область видимости(работы) переменной
variable. И после закрытия скобки вызывает Dispose() переменной?
    


Ответы

Ответ 1



Правильно ли я понял, что данная конструкция создает область видимости(работы) переменной variable. Да. Если переменная объявлена (и проинициализирована) в блоке, то ее область видимости ограничена только блоком using. Также возможно использовать блок using c уже объявленной и проинициализированной переменной. Для чего это может понадобиться, см. ниже. И после закрытия скобки вызывает Dispose() переменной? Да. Такой участок: using (var x = ...) { x.Foo(); } Преобразовывается компилятором в следующий код: var x = ...; try { x.Foo(); } finally { if (x != null) { ((IDisposable)x).Dispose(); } } Обратите внимание, что инициализация находится вне блока try. Соответственно, использование в using уже проинициализированной переменной является более компактным способом записи try/finally с вызовом Dispose().

C# конструктор без параметров базового класса

#c_sharp #c_sharp_faq


public class BaseClass
{
    public int X;
    public BaseClass() { X = 1; }
}
public class Subclass : BaseClass
{
    public Subclass() { Console.WriteLine(X); } //1
}


Код взят из учебника и мне непонятно, почему конструктор базового класса тоже вызывается.
Ведь мы не вызываем его через ":base"
    


Ответы

Ответ 1



Базовый конструктор по умолчанию вызывается всегда, если он присутствует в базовом классе*. Стоит помнить, что конструктор по умолчанию генерируется всегда, если отсутствуют другие конструкторы. Явный вызов базового конструктора через ключевое слово base нужен в том случае, когда у вас есть один или несколько конструкторов с параметрами, и вам нужно/вы хотите пробросить все или часть этих параметров в базовый класс, указав при этом конкретный конструктор базового класса: public class BaseClass { public BaseClass(string someParam) { ... } } public class ChildClass: BaseClass { public ChildClass(string someParam, int someParam2) : base(someParam) { ... } } Вы также можете использовать его и в вашем случае, однако в этом нет нужды и компилятор это подскажет: public class BaseClass { public int X; public BaseClass() { X = 1; } } public class Subclass : BaseClass { public Subclass() : base() { Console.WriteLine(X); } } *существуют исключения: например, когда конструктор по умолчанию наследника вызывает конструктор с параметрами базового класса, используя base()

Ответ 2



При создании экземпляра класса наследника всегда вызывается конструктор базового класса. ключевое слово base позволяет указать какой именно конструктор базового класса будет вызван. При отсутствии указания, компилятор будет пытаться вызывать из базового класса конструктор без параметров, и если в базовом классе такой конструктор будет отсутствовать, то будет ошибка, о том, что базовый тип не содержит конструктора без параметров. using System; class B { public B(int i) {Console.WriteLine("Base non-default constructor");} } class D : B { public D(int i) {} // error CS1729: The type `B' does not contain a constructor that takes `0' arguments } public class Test { public static void Main() { new D(42); } }

воскресенье, 8 декабря 2019 г.

Завершение потока из другого потока

#c_sharp #многопоточность #c_sharp_faq


Например, есть несколько практически одинаковых потоков, которые могут длиться до
бесконечности

инициализация:

 ParameterizedThreadStart pts = new ParameterizedThreadStart(runMethod);
 Thread t = new Thread(pts);            
 t.Start(values);


поток:

 void run(object p)
 {
      // нужен поток, который повторяет одно и то же с некоторой переодичностью,
поэтому так:

      while(true)
      {
           // что-то делаем
           doAnything(p);

           Thread.Sleep(60000);
      }
 }


Вопрос в том, как их адекватно прервать? Было бы здорово, если их прервать можно
было прервать пока они спят или хотя бы после doAnything();

можно в основном потоке сделать что-то вроде

 Thread t = new Thread(pts);            
 t.Start(values);
 ...
 t.Abort();


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


Ответы

Ответ 1



Красивее всего это делается через механизм CancellationToken. Этот механизм специально создан для передачи сообщений об остановки и отмене между потоками: void run(object p) { var ct = (CancellationToken)p; do { // что-то делаем doAnything(p); } while (!ct.IsCancellationRequested && !ct.WaitHandle.WaitOne(60000)); } Здесь я использую ожидание на событии как прерываемую альтернативу Thread.Sleep. Если ожидание успешно - значит, поток надо останавливать. Если неуспешно (тайм-аут ожидания) - значит, можно продолжать работу. Перед ожиданием я проверяю IsCancellationRequested, чтобы не создавать событие ядра когда этого не требуется. Создание такого потока: using (var cts = new CancellationTokenSource()) { new Thread(run).Start(cts.Token); ... cts.Cancel(); } Также вместо потока можно использовать задачу (Task.Run). В таком случае для ожидания лучше использовать Task.Delay, это позволит обойтись вовсе без объектов ядра: async Task run(CancellationToken ct) { do { // что-то делаем doAnything(p); await Task.Delay(60000, ct); } while (!ct.IsCancellationRequested); } Task.Run(() => run(cts.Token)); Бонусом к использованию CancellationToken идет возможность передачи ct дальше в doAnything - благодаря чему вы можете тонко выбирать, в какие моменты допустимо прерывание обработки. Если вы будете использовать асинхронный вариант - то практически все асинхронные функции стандартной библиотеки также умеют принимать CancellationToken, что позволяет безопасно прервать любую долгую операцию, если вы того пожелаете. В более старых рантаймах, где механизма CancellationToken нет, для той же цели можно использовать ManualResetEvent. Принцип тот же - вместо вызова Thread.Sleep ждем на событии и проверяем результат.

Ответ 2



Есть более подробный ответ здесь и здесь Самый простой вариант создать boolean переменную, которая отслеживает запрос на завершение потока. Данный вариант не позволяет прервать Thread.Sleep. boolean volatile isRunning = true; void run(object p) { while(true) { if (!isRunning) break; // exit if not running doAnything(p); Thread.Sleep(60000); } } Другой вариант использовать CancellationTokenSource. Подходит начиная с .net 4

суббота, 7 декабря 2019 г.

Анонимные типы в c# и их особенности?

#c_sharp #c_sharp_faq


Начал изучать анонимные типы в C#.
Автор приводит пример синтаксиса анонимного типа var instance = new { Name = "Alex",
Age = 27 }; и предлагает последовательно — шаг за шагом добавить на эту строку дополнительные
элементы синтаксиса, чтобы данная строк стала более узнаваемая "читаемая" для нас (напоминаю:
автор это делает т.к пример и весь курс — учебный) т.е. превратив выше приведенную строку в

var instance = new MyClass() { Name = "Alex", Age = 27 };


говоря, что мы добавили имя конструктора по умолчанию и круглые скобки для приема
аргументов конструктора, и тут же демонстрируеь, что добавляя это — студия предлагает
сгенерировать нам свойства для Age и для Name в классе MyClass(класс он создал сам
— опять же для примера, но сказал, что в анонимных типах при генерации свойств класс
также создаётся автоматически).
В классе сгенерировались автореализуемые свойства


Но автор сказал, что они должны быть только для чтения, т.е. в (ну в данном случае
посто нужно убрать set — ведь студия генерировала свойства как для обычных полей класса
когда мы дописала имя конструктора по умолчаню после ключевого слова new).
И это подводит меня к вопросу №3 (вопросы №1 и №2 представлены ниже). Если автогенерируемые,
автореализуемые свойства в также автоматически созданном классе (назовем его MyClass)
только для чтения, то каким образом в блоке инициализатора на строке var instance =
new { Name = "Alex", Age = 27 }; мы вообще можем присваивать этим полям значения?
Вот скриншот первого примера, в котором меня интересует комментарий, в котором у
меня и возникают все эти вопросы.


Вопрос №1: т.е. компилятор каждый раз создает новое имя для анонимного типа, который
в свою очередь является ссылочным типом?

Вопрос №2: т.е. приложение не может обращаться ссылке (т.к. тип ссылочный) к 
новому имени созданному компилятором.

Вопрос №3 представлен вначале. 
    


Ответы

Ответ 1



Да, так задумано. Компилятор создаёт имя наподобие <>f__AnonymousType0, которое невозможно использовать из C#. К нему невозможно получить доступ просто так, потому что такие имена нельзя использовать в C#. Но вы легко можете увидеть это имя при помощи рефлексии: class Program { public static void Main() { var o = new { x = 5, y = 8 }; foreach (var t in Assembly.GetExecutingAssembly().GetTypes()) Console.WriteLine(t.Name); } } выдаёт <>f__AnonymousType0`2 Program Settings (`2 означает, что класс на самом деле является generic-классом по причинам, указанным здесь) Программа вполне может обращаться по ссылке к значениям этого типа. Просто она не может получить имя типа, чтобы, например, объявить тип возвращаемого значения. Обращение на чтение работает: var o = new { x = 5, y = 8 }; // var необходим, т. к. мы не можем назвать имя Console.WriteLine(o.x); // обращение по ссылке Нет, присваивать свойствам анонимного типа нельзя. Они создаются вовсе без сеттера. На самом деле вот такой код: var o = new { x = 5, y = 8 }; порождает (примерно) следующий класс: [DebuggerDisplay("{ x = {x}, y = {y} }", Type = "")] public sealed class Anonymous { private readonly TX field_x; private readonly TY field_y; public TX x { get { return field_x; } } public TY y { get { return field_y; } } [DebuggerHidden] public Anonymous(PX x, PY y) { field_x = x; field_y = y; } public override bool Equals(object value) { /* тут имплементация */ } public override int GetHashCode() { /* тут имплементация */ } public override string ToString() { /* тут имплементация */ } } а ваш вызов превращается компилятором в следующее: Anomymous o = new Anonymous(5, 8); Вы видите, что на самом деле присвоения свойствам нету, а есть просто вызов конструктора. Поэтому в дальнейшем запись в эти же свойства невозможна.

среда, 4 декабря 2019 г.

Инициализация структур C#

#c_sharp #net #c_sharp_faq


Почему в значимых типах C# необходимо инициализировать все поля, при наличии конструктора? 

Например, следующий код не скомпилируется

struct AAA
{
    public int A;
    public string C;

    public AAA(int a)
    {
        A = a;
    }
}



  Поле ААА.С должно быть полностью определено до возврата управления в
  вызывающий метод


Но если уберем конструктор, то все компилируется 

struct AAA
{
    public int A;
    public string C;
}

    


Ответы

Ответ 1



Дело в том, что для структур, в отличие от классов, нету инициализации полей по умолчанию (ради эффективности). Если вы не определяете конструктор, то у вас есть конструктор по умолчанию, который инициализирует все поля нулевым значением (default соответствующего типа). Если вы определяете свой конструктор, то достаточно вызвать конструктор по умолчанию: public AAA(int a) : this() { A = a; } Без этого поле C инициализировано не было бы, и значение было бы не определено. Такие ситуации C#, в отличие от C++, не допускает.

воскресенье, 1 декабря 2019 г.

Почему запрещено наследование от значимых типов?

#c_sharp #наследование #struct #c_sharp_faq


Почему запрещено наследование от значимых типов, например struct? То что struct sealed
- это понятно:) Но почему её сделали sealed?
    


Ответы

Ответ 1



Проблема вот в чём. Допустим, разрешено было бы наследоваться от структур. Рассмотрим такой код: class C1 { int x; } class C2 : C1 { int y; public override string ToString() { return y.ToString(); } } Вы можете написать так: C1 c = new C2(); и вызвать c.ToString();. Поскольку c является лишь ссылкой на данные, проблем с вызовом не возникает. Это стандартная фича, базовое полиморфное поведение ссылочных типов. Представьте себе теперь аналогичную ситуацию: struct S1 { int x; } struct S2 : S1 { int y; public override string ToString() { return y.ToString(); } } S1 s = new S2(); Что будет в s? Из соображений эффективности структуры хранятся в памяти как есть, то есть, просто набор полей, а не ссылка на них. Это значит, что под s отводится столько байт, сколько занимает S1, и для поля y там просто нет места! Что в этом случае должен вернуть вызов s.ToString()? Аналогичная проблема возникает при передаче параметров производного типа в функцию, требующую аргумент базового типа. Эта проблема называется slicing, и она актуальна для дизайна многих языков программирования. Например, она присутствует в C++. Решение в C# — отказаться от наследования структур: допустить неработающий полиморфизм при наследовании — плохая идея. При таком решении slicing в C# исключён. Другие языки решают проблему по-другому. Например, в C++ настоящим типом объекта s будет просто S1, а не S2, и y просто потеряется. Создатели языка C# считают такое поведение неинтуитивным, ведущим к проблемам и ошибкам, поэтому они пошли по другому пути. Если вам нужно наследование, в C# нужно воспользоваться классами, или перейти к композиции. Проблему можно было бы решить, если бы структуры хранились точно так же, как и классы: не значение, а ссылка на него. При этом операции наподобие присвоения должны были бы для сохранения семантики копировать не ссылку, а значение всё равно. Но такой подход, хоть и разрешил бы наследование структур, сильно ухудшил бы их эффективность: каждая операция со структурой стала бы медленнее из-за лишнего косвенного доступа, плюс аллокация структур в общем случае была бы вытеснена из стека в кучу. Архитекторы языка разменяли полиморфизм структур на их эффективность.

C# List vs LinkedList vs Array

#c_sharp #массивы #list #c_sharp_faq


List vs LinkedList vs Array

В каких случаях что лучше использовать?

C#-FAQ вопрос полезный для прохождения собеседований, а так же весьма полезен с теоретической
точки зрения.
    


Ответы

Ответ 1



Я сделал некоторые тесты. Думаю, многим будет интересно посмотреть на результаты. Исходники тестов по линке: https://github.com/ukushu/DataStructuresTests.git Короткие выводы: Array нужно использовать: Максимально часто, если это возможно (быстродействие и оптимальность памяти) Если не нужно добавлять ячейки Если ожидаемый вес < 85000b Если нужна Random Access Speed List нужно использовать: Если нужно добавлять ячейки в конец списка (в большом/малом количестве) Если нужно добавлять ячейки в начало/середину списка (в малом количестве) Если ожидаемый вес < 85000b Если нужна Random Access Speed Предпочтительно инициализировать с уже набранным количеством элементов, если это возможно. LinkedList Если нужно добавлять ячейки в начало/середину/конец листа в большом количестве Если нужен только последовательный доступ (не нужны индексы для random access) Хорошо подходит для хранения увесистых обьектов в относительно небольшом количестве т.к. под каждую ячейку так же нужно выделять память на 2 ссылки. Детальные выводы: светлокрасный фон -- плохой результат. желтый фон -- нормальный(средний) результат. светлозеленый фон -- хороший результат. И немного информации, которую просто интересно узнать: LinkedList внутри вообще не является List в языках .NET. LinkedList. Он даже не реализован на IList. Вот почему там нет индексов и методов, которые связаны с индексами. LinkedList - это node-pointer based collection. В .NET это реализовано в doubly linked implementation, то есть каждый элемент ссылается на предыдущий и последующий за ним. А так же то, что данные будут фрагментированы. Разные объекты в листе будут находится в разных местах оперативной памяти. Так же это значит, что будет использовано больше памяти под LinkedList чем под List или Array, как и видно из тестов. Array как и List (в .Net - это враппер вокруг Аrray с возможностью изменения размера) резервируют память оперативки как один продолжительный блок. Если общий размер элементов размер > 85000 bytes, он будет переразмещен в Large Object Heap. Что в свою очередь может привести к heap fragmentation -- что-то вроде легкой формы memory leak. List в памяти будет занимать значительно меньше памяти, если его создавать не пустым, а приблизительно наперед нужного размера. То есть, если вы ожидаете что в листе должно быть 1000 ячеек, то он будет занимать как массив данных с 1000 ячеек. Если же вы его создали пустым и заполнили до количества в 1000 ячеек, он будет занимать больше, как показано в тестах. ВАЖНО Если кто нашел ошибки -- укажите пожалуста в коментариях. Независимо от того, ошибки в коде, или в моих тестах, или моих выводах. :)

Как в C# правильно сравнивать строки

#c_sharp #c_sharp_faq


Как в C# правильно сравнивать строки: Equals или ==?

string str1 = "s";
string str2 = "s";

Console.WriteLine("eq: " + str1.Equals(str2));
Console.WriteLine("==: " + (str1 == str2));


В обоих случаях результат True, хотя String является классом и оператор == должен
был сравнить ссылки.

IlDasm показал, что создаются 2 переменные и сравниваются согласно методам Equals
и == (op_Equality)

  IL_0000:  nop
  IL_0001:  ldstr      "s"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "s"
  IL_000c:  stloc.1
  IL_000d:  ldstr      "eq: "
  IL_0012:  ldloc.0
  IL_0013:  ldloc.1
  IL_0014:  callvirt   instance bool [mscorlib]System.String::Equals(string)
  IL_0019:  box        [mscorlib]System.Boolean
  IL_001e:  call       string [mscorlib]System.String::Concat(object, object)
  IL_0023:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0028:  nop
  IL_0029:  ldstr      "==: "
  IL_002e:  ldloc.0
  IL_002f:  ldloc.1
  IL_0030:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0035:  box        [mscorlib]System.Boolean
  IL_003a:  call       string [mscorlib]System.String::Concat(object, object)

    


Ответы

Ответ 1



В C# правильно сравнивать строки и через ==, и через Equals. Но более предпочтительным будет сравнивать через ==. Почему так? Метод Equals подразумевает сравнение значений объектов ссылочного типа, он объявлен как virtual и для строк он перегружен и сравнивает их, как и предполагается, по значению. В Ваших классах Вы должны давать свою реализацию для него. Иначе он будет вести себя как ReferenceEquals и для ссылок, которые указывают не на один объект будет давать false, хоть они и будут равны по значению. Оператор == для строк представляют свою реализацию, отличную от стандартной для всех других объектов ссылочного типа. Если сравниваемые ссылки имеют тип System.String, то он сначала сравнит указывают ли ссылки на один тот же объект и если нет, то будет сравнивать две ссылки типа System.String по значению. Но маленькое замечание, если мы сравниваем объект типа System.String (string) с объектом типа System.Object (object), который указывает на строку, они будут сравниваться по значениям. Пример: string str = "str"; object obj = "str"; bool result = str.Equals(obj); result будет равен true, так как obj приведётся к типу string, потому что метод Equals мы вызвали у строки (объекта типа string). Но если мы сравниваем объект типа System.Object (object), который указывает на строку, с объектом типа System.String (string) они будут сравниваться по ссылке. Пример: string str = "str"; object obj = "str"; bool result = obj.Equals(str); result будет равен false, так как str приведётся к типу object, потому что метод Equals мы вызвали у объекта типа object. Поэтому в данном случае важно привести object к типу string

Ответ 2



Оператор == не обязан сравнивать только ссылки. В C# операторы можно переопределять, и он, естественно, переопределен для множества типов. Сравнение ссылок - это просто поведение по умолчанию для типов, для которых оператор == не переопределен. Для типов, для которых переопределен оператор ==, скорее всего будет переопределен и Equals, причем результат для обоих способов сравнения скорее всего будет совпадать (если не будет - значит при проектировании типа его автор просто ошибся). Вот реализация == для String из reference source: public static bool operator == (String a, String b) { return String.Equals(a, b); } public static bool Equals(String a, String b) { if ((Object)a==(Object)b) { return true; } if ((Object)a==null || (Object)b==null) { return false; } if (a.Length != b.Length) return false; return EqualsHelper(a, b); } А вот реализация нестатического Equals оттуда же: public override bool Equals(Object obj) { if (this == null) //this is necessary to guard against reverse-pinvokes and throw new NullReferenceException(); //other callers who do not use the callvirt instruction String str = obj as String; if (str == null) return false; if (Object.ReferenceEquals(this, obj)) return true; if (this.Length != str.Length) return false; return EqualsHelper(this, str); } Они отличаются только небольшой поправкой на нестатичность. Обе сравнивают ссылки и проверяют случай null != null, потом сравнивают длину на неравенство, и только потом - значение.

Ответ 3



В C# для строк перегружен и оператор == и метод Equals, так как строки чаще требуется сравнивать по значению, нежели по ссылкам. В случае с оператором == сначала происходит сравнение указателей строк, и если они равны, то метод возвращает true. В противном случае проверяется равенство операндов null (если хотя бы один из них равен null, возвращается false). Затем сравниваются длины строк. А затем происходит посимвольное сравнение строк (то есть сравнение по значению). Это делается с использованием указателей в неуправляемом коде (очевидно из соображений производительности), а также применяется размотка циклов (очевидно из тех же соображений, но это уже малоинтересные в данном случае детали). Если интересны детали, посмотреть можете здесь

суббота, 30 ноября 2019 г.

Как проверить тип объекта во время выполнения программы на C#?

#c_sharp #c_sharp_faq


Как проверить тип объекта во время выполнения программы на C#?    


Ответы

Ответ 1



Оператор is языка C# проверяет является ли объект экземпляром типа или производного от него типа. if (obj is MyObject) { } Справка из MSDN: оператор is.

Ответ 2



Только is вернёт true, если obj является подклассом MyObject, иногда это нежелательно. Для точного сравнения типов пишите так: obj.GetType() == typeof(MyObject)

C#. В чем разница между реализацией обобщенного класса через параметризацию типом и через тип object?

#c_sharp #generics #объекты #c_sharp_faq


Меня интересует этот вопрос с точки зрения производительности. Про то что object
требует приведение типов и из-за этого можно неявно допустить ошибку, я знаю.

UPD. Этот вопрос был задан на собеседовании. В качестве примера добавил 2 класса:

class SomeClass
{
    private T[] storage;
}

class SomeClass
{
    private object[] storage;
}

    


Ответы

Ответ 1



В том случае, если вы используете значимые типы, вы получаете дополнительные накладные расходы на упаковку и последующую распаковку. var numbers = new List(); numbers.Add(1); // упаковка №1 numbers.Add(2); // упаковка №2 int n1 = (int)numbers[0]; // распаковка №1 int n2 = (int)numbers[1]; // распаковка №2 Ну и как вы сами упомянули, такое использование не является типобезопасным. Обобщенные коллекции и были введены в .NET 2.0 как раз для того, чтобы избежать этих проблем. В более ранних версиях использовался ArrayList, который, по большому счету, является аналогом List. UPD В приведенном вами примере все то же самое: в случае, если в массив storage будут добавляться экземпляры значимых типов, будет происходить упаковка.

Ответ 2



Преимущества обобщенного кода, над не обобщенным: Безопасность типов. Когда обобщенный алгоритм применяется с конкретным типом, компилятор и CLR понимают это и следят за тем, чтобы в алгоритме использовались лишь объекты, совместимые с этим типом данных. Попытка использования несовместимого объекта приведет к ошибке на этапе компиляции или исключению во время выполнения. Объявив SomeType some = new SomeType() в вашей переменной storage можно будет хранить только объекты типа Stream или производные от него. Проверка будет происходить на этапе написания кода, т.е. сразу же выдаст предупреждение, в отличие от object - на этапе выполнения. Более простой и понятный код. Поскольку компилятор обеспечивает безопасность типов, в исходном тексте требуется меньше операция приведения типов, а такой код проще писать и сопровождать. Повышение производительности. До появления обобщений один из способов определения обобщенного алгоритма заключался в таком определении всех его членов, чтобы они «умели» работать с типом данных Object. Чтобы алгоритм работал с экземплярами значимого типа, перед вызовом членов алгоритма среда CLR должна была упаковать этот экземпляр. Упаковка требует выделения памяти в управляемой куче, что приводит к более частым процедурам уборки мусора, а это, в свою очередь, снижает производительность приложения. Поскольку обобщенный алгоритм можно создать для работы с конкретным значимым типом, экземпляры значимого типа могут передаваться по значению и CLR не нужно выполнять упаковку. Операции приведения типа также не нужны, поэтому CLR не нужно контролировать безопасность типов при их преобразовании, что также ускоряет работу кода. Для оценки производительности был использован не обобщенный ArrayList из библиотеки классов FCL и обобщенный List. Метод Main - вызов двух тестов public static void Main() { ValueTypePerfTest(); ReferenceTypePerfTest(); } Тестирование значимых типов: private static void ValueTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List")) { List l = new List(); for (Int32 n = 0; n < count; n++) {          l.Add(n);                 // Без упаковки          Int32 x = l[n];           // Без распаковки }        l = null; // Для удаления в процессе уборки мусора } using (new OperationTimer("ArrayList of Int32")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) {          a.Add(n);                  // Упаковка          Int32 x = (Int32) a[n];    // Распаковка }        a = null; // Для удаления в процессе уборки мусора } } Тестирование ссылочных типов: private static void ReferenceTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List")) { List l = new List(); for (Int32 n = 0; n < count; n++) {        l.Add("X");                   // Копирование ссылки        String x = l[n];              // Копирование ссылки }      l = null; // Для удаления в процессе уборки мусора } using (new OperationTimer("ArrayList of String")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) {          a.Add("X");                 // Копирование ссылки          String x = (String) a[n];   // Проверка преобразования        }                             // и копирование ссылки        a = null; // Для удаления в процессе уборки мусора } } Класс для оценки времени выполнения операций internal sealed class OperationTimer : IDisposable { private Int64 m_startTime; private String m_text; private Int32 m_collectionCount; public OperationTimer(String text) { PrepareForOperation(); m_text = text; m_collectionCount = GC.CollectionCount(0);      // Эта команда должна быть последней в этом методе      // для максимально точной оценки быстродействия m_startTime = Stopwatch.StartNew(); } public void Dispose() {      Console.WriteLine("{0} (GCs={1,3}) {2}", (m_stopwatch.Elapsed),         GC.CollectionCount(0), m_collectionCount, m_text); } private static void PrepareForOperation() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } Результаты тестирования 00:00:01.6246959 (GCs= 6) List 00:00:10.8555008 (GCs=390) ArrayList of Int32 00:00:02.5427847 (GCs= 4) List 00:00:02.7944831 (GCs= 7) ArrayList of String Вывод С типом Int32 обобщенный алгоритм List работает гораздо быстрее, чем не обобщенный алгоритм ArrayList. Более того, разница огромная: 1,6 секунды против 11 секунд, то есть в 7 раз быстрее! Кроме того, использование значимого типа (Int32) с алгоритмом ArrayList требует множества операций упаковки, и, как результат, 390 процедур уборки мусора, а в алгоритме List их всего 6. Результаты тестирования для ссылочного типа не столь впечатляющие: временные показатели и число операций уборки мусора здесь примерно одинаковы. Поэтому в данном случае у обобщенного алгоритма List реальных преимуществ нет. Тем не менее помните, что применение обобщенного алгоритма значительно упрощает код и контроль типов при компиляции. Источник: Jeffrey Richter "CLR via C#"

пятница, 29 ноября 2019 г.

Префикс перед string: '$'

#c_sharp #c_sharp_faq


Прошерстил весь свой справочник по C# и не смог найти что такое '$'.

Я понял только то, что это чем-то похоже на verbatim string '@'.

Console.WriteLine($"?");


Как это влияет на строку?
    


Ответы

Ответ 1



Префикс $ перед строкой обозначает, что далее идет интерполированная строка. Интерполяция используется для удобного конструирования строки. При этом выражение интерполированных строк может выглядеть как шаблон строки, в котором могут быть использованы выражения языка. Интерполированные строки легче в понимании относительно аргументов, по сравнению с композитным форматированием Пример: var s = $"Name = {name}, hours = {hours:hh}"; По сравнению с string.Format var s = string.Format("Name = {0}, hours = {1:hh}", name, hours); Важное отличие от того же string.Format: String interpolation is transformed at compile time to invoke an equivalent string.Format call. This leaves in place support for localization as before (though still with traditional format strings) and doesn’t introduce any post compile injection of code via strings. Интерполированная строка в момент компиляции трансформируется в эквивалентный вызов string.Format. Это оставляет поддержку локализации как прежде (хотя все еще с традиционным форматированием строк) и не позволяет добавлять любые инъекции кода в строку, после компиляции. Данная возможность появилась с выходом C# 6. Подробнее можно почитать в справке на MSDN

Ответ 2



Это интерполяция строк, удобная вещь, которая позволяет написать выражение: int i=1; Console.WriteLine($"i={i}"); Подробнее тут: https://msdn.microsoft.com/ru-ru/library/dn961160.aspx

Разница между catch, catch(Exception) и catch(Exception ex)

#c_sharp #исключения #try_catch #c_sharp_faq


Допустим, я не планирую использовать переменную ex и мне надо, чтобы обрабатывалась
любая ошибка.

try
{
    ...
}
catch(Exception ex)
{
    return;
}


Надо ли в таком случае объявлять переменную ex?

Или можно сделать так:

try
{
    ...
}
catch(Exception)
{
    return;
}


Или вообще вот так:

try
{
    ...
}
catch
{
    return;
}


В чем разница и как правильнее?
    


Ответы

Ответ 1



Переменную нужно объявлять, если в дальнейшем планируется её как-то использовать. Если важен только сам факт перехвата - достаточно указать всего лишь тип. Различие в catch(Exception) и пустом catch имеет место быть, если нужно перехватывать не CLS-совместимые исключения. Более подробнее об этом можно почитать на msdn. По ссылке как раз видно, что есть смысл и в таком коде: catch(Exception) { ... } catch { ... }

Ответ 2



Исключения также можно выстраивать одно за другим по мере увеличения. Если error не поппадает ни в одно исключений, то обезательно попадет в catch (Exception ex) как показанно на примере ниже. finally блок выполняется в при любых условиях. Console.WriteLine("Enter a number:"); while (true) { try { int yourNum = Convert.ToInt32(Console.ReadLine()); if (yourNum < 1 || yourNum > 10) { throw new Exception("Number must be between 1 and 10"); } Console.WriteLine("Your number is {0} !", yourNum); break; } // ловит ошибку неправильного формата. catch (FormatException) { Console.WriteLine("Not a number. Please try again"); } // ловит ошибку если число не попадает в допусимый ряд int catch (OverflowException) { Console.WriteLine("Not a valid integer. Pelase try again"); } // если ни один из предыдущих catch не скработал ловим изящно (gracefully) в ex переменную где ex.ToString() выводит нам на экран в string формате catch (Exception ex) { Console.WriteLine(ex.ToString()); } // код выполняется независимо есть ли ошибка или нет. finally { Console.WriteLine("Thanks for playing!"); } }

Ответ 3



Если вы хотите отловить любое исключение и контекст не важен, пишите: try { //... } catch { //... } Если важен тип исключения то: try { //... } catch (Exception) { //... } Но если логика завязана ещё и на данных в классе исключения то соответственно: try { //... } catch (Exception ex) { //... }

Ответ 4



1) Ловим конкретный тип исключения и заносим его в переменную. Переменную можно проанализировать в блоке и ,например, попробовать восстановить правильный ход программы 2) Ловим конкретный тип исключения, но само исключение не анализируем 3) Не уверен, но скорее всего ловятся абсолютно все исключения. P.S Так как все исключения наследуются от Exception, то 2 и 3 примеры аналогичны.