Deep Dive C# by Sergey Teplyakov

39
Deep Dive Сергей Тепляков, Visual C# MVP .NET Architect at Luxoft SergeyTeplyakov.blogspot.com

description

 

Transcript of Deep Dive C# by Sergey Teplyakov

Page 1: Deep Dive  C# by Sergey Teplyakov

Deep DiveСергей Тепляков, Visual C# MVP

.NET Architect at LuxoftSergeyTeplyakov.blogspot.com

Page 2: Deep Dive  C# by Sergey Teplyakov

Анасколько глубоко ?будемнырять

Page 3: Deep Dive  C# by Sergey Teplyakov

?Настолько глубокоclass X { public const int Value = 1000; }

static int Foo(Func<int?, byte> x, object y) { return 1; }static int Foo(Func<X, byte> x, string y) { return 2; }

var a = Foo(X => (byte)X.Value, null);unchecked{ Console.WriteLine(a);}

unchecked{ var a = Foo(X => (byte)X.Value, null); Console.WriteLine(a);}

unchecked{ var a = Foo(X => (byte)X.Value, (object)null); Console.WriteLine(a);} Увидим 1Увидим 2Снова 1!!!!

Page 4: Deep Dive  C# by Sergey Teplyakov

! ! !Нет Нет Нет• Подробнее об этом треше -

http://rsdn.ru/forum/dotnet/3272728.flat• См. этюды nikov-а на rsdn.ru –

http://rsdn.ru/Forum/?fuid=55905• Кури “The C# Programming Language” by Hejlsberg et al!

Page 5: Deep Dive  C# by Sergey Teplyakov

Чтонужнодляработы цикла foreach?

• IEnumerable?• IEnumerable of T?• Что-то еще?

• Нужен метод GetEnumerator, возвращающий объект с методом MoveNext и свойством Current!

Page 6: Deep Dive  C# by Sergey Teplyakov

В F# …пошлиещедальше• Поддержку цикла for можно добавить с помощью методов

расширения!

type Int32 with    // Получаем список квадратов чисел от 1 до текущего значения    member x.GetEnumerator() =        ({1..x} |> Seq.map(fun x -> x*x)).GetEnumerator()

// Выводит 1 4 9 16 25for n in 5 do printf "%d " n

Page 7: Deep Dive  C# by Sergey Teplyakov

Утинаятипизация

Если кто-то ходит, как утка, и крякает, как утка, то это и есть может быть утка индюшка с утиным адаптером...

Page 8: Deep Dive  C# by Sergey Teplyakov

« » Утинаятипизация в C#• foreach• Требуется GetEnumerator, MoveNext и свойство Current• http://sergeyteplyakov.blogspot.com/2012/08/duck-typing-forea

ch.html• LINQ (Query Comprehension syntax)• Требуются методы Select, Where, GroupBy etc.

• Collection initializer• Требуется метод Add

• C# 5.0 Async Features• Требуются GetAwaiter() и методы BeginAwait(Action) и

EndAwait(), GetResult() и свойства IsCompleted.• System.Runtime.CompilerServices.ExtensionAttribute• Методы расширения завязаны не на конкретный тип

атрибутов!

Page 9: Deep Dive  C# by Sergey Teplyakov

…Блокиитераторовpublic static IEnumerable<string> ReadByLine(string path){    if (string.IsNullOrEmpty(path))        throw new ArgumentNullException("path");

    using (var sr = new StreamReader(path))    {        string s;        while ((s = sr.ReadLine()) != null)            yield return s;    }}

var seq = ReadByLine(null);              // 1var s = seq.Select(line => line.Length); // 2Console.WriteLine(s.Max());              // 3

Все ли нормально с кодом?

Этот же подход используется и для

асинхронных методов!

Когда получим исключение?

Код до первого yield return вызовется при

первом вызове метода MoveNext!

Page 10: Deep Dive  C# by Sergey Teplyakov

Какоеисключение?получим

class Foo{ public Foo() { throw new Exception("Ooops!!"); }}

static T Create<T>() where T : new(){ var instance = new T(); // Write to log some message return instance;}

var f = Create<Foo>();

Какое исключение получим?

Page 11: Deep Dive  C# by Sergey Teplyakov

?Почему• Используется reflection (Activator.CreateInstance). • Все обобщения должны содержать одну реализацию!• Вызов метода через Reflection всегда «оборачивает»

исходное исключение в TargetInvocationException

• «Все нетривиальные абстракции текут» Джоэл Спольски

Page 12: Deep Dive  C# by Sergey Teplyakov

Повторная генерацияисключений• Делаем "правильную" фабрику. Наивная реализация:

public static T CreateInstanceNaive<T>() where T : new(){    try    {        return new T();    }    catch (TargetInvocationException e)    {        // Исходный стек вызовов потерян, теперь все  // будут думать, что виноваты мы!        throw e.InnerException;    }}

Page 13: Deep Dive  C# by Sergey Teplyakov

ИспользуемExceptionDispatchInfopublic static T CreateInstance<T>() where T : new(){    try    {        return new T();    }    catch(TargetInvocationException e)    {        ExceptionDispatchInfo di =  ExceptionDispatchInfo.Capture(e.InnerException);         di.Throw();        // компилятор C# не знает, что эта точка недостижима        return default(T);    }}

Page 14: Deep Dive  C# by Sergey Teplyakov

Quiz #3. Исключениев блоке finally

public static void FinallyThatThrows() { try { throw new Exception("1"); } catch(Exception e) { throw new Exception("2"); } finally { throw new Exception("3"); }}

try { FinallyThatThrows(); }catch(Exception e) { Console.WriteLine(e.Message);}

Что получим? "1", "2" или "3"?

Page 15: Deep Dive  C# by Sergey Teplyakov

Quiz # 4. Маскирование исключенийвблоке using

class CustomDisposable : IDisposable{    public void Dispose()    {        throw new InvalidOperationException("Ooops!!");    }}

// Какое исключение перехватывать?using (var disposable = new CustomDisposable()){    throw new FileNotFoundException();}

Page 16: Deep Dive  C# by Sergey Teplyakov

Подходыкпотеряннымисключениям• C# - побеждает исключение из finally. Исходное теряем• С++• Исключение в деструкторе – UB ;)• std::unexpected, если деструктор вызван при раскрутке стека

• Java• Исключение в блоке try-finally

• Тоже самое, что и в C#• try-with-resources

• Побеждает исключение из finally, но исходное остается в скрытых исключениях

Page 17: Deep Dive  C# by Sergey Teplyakov

Quiz #6. Созданиеобъектаclass Base : IDisposable{    public Base()    {        // Выделяем ресурсы!    }

    public void Dispose()    {        Console.WriteLine("Base.Dispose");    }}

class Derived : Base{    public Derived(object data)    {        if (data == null)            throw new ArgumentNullException("data");

        // Oops!!!    }}

Что будет в этом случае?

Page 18: Deep Dive  C# by Sergey Teplyakov

…Обработкаошибокpublic static IEnumerable<string> ReadByLine(string path){    if (string.IsNullOrEmpty(path))        throw new ArgumentNullException("path");

    using (var sr = new StreamReader(path))    {        string s;        while ((s = sr.ReadLine()) != null)            yield return s;    }}

var seq = ReadByLine(null);              // 1var s = seq.Select(line => line.Length); // 2Console.WriteLine(s.Max());              // 3

Все ли нормально с кодом?

Этот же подход используется и для

асинхронных методов!

Когда получим исключение?

Код до первого yield return вызовется при

первом вызове метода MoveNext!

Page 19: Deep Dive  C# by Sergey Teplyakov

Корректнаяреализацияpublic static IEnumerable<string> ReadByLine(string path){    if (string.IsNullOrEmpty(path))        throw new ArgumentNullException("path");

    return ReadByLineImpl(path);}

private static IEnumerable<string> ReadByLineImpl(string path){    using (var sr = new StreamReader(path))    {        string s;        while ((s = sr.ReadLine()) != null)  yield return s;    }}

Наличие блока итераторов принципиально изменяет

реализацию метода!

var seq = ReadByLine(null);              // 1var s = seq.Select(line => line.Length); // 2Console.WriteLine(s.Max());              // 3

Page 20: Deep Dive  C# by Sergey Teplyakov

?Вчемразница• Исключение при вызове метода – ошибка в вызывающем

коде• Исключение во время перебора элементов –

ошибка/проблема в вызываемом коде!

Page 21: Deep Dive  C# by Sergey Teplyakov

Необязательные аргументыи

полиморфизмclass Base { public virtual void Foo(int i = 42) { Console.WriteLine("Base.Foo: i = {0}", i); }}

class Derived : Base { public override void Foo(int i = 0) { Console.WriteLine("Derived.Foo: i = {0}", i); }}

void Main() { Derived d = new Derived(); Base b = d;

b.Foo(); d.Foo();}

Вызываем через Base!Вызываем через

Derived!

Page 22: Deep Dive  C# by Sergey Teplyakov

?Чтобудет в этомслучае// 1void WithDateTime(DateTime dt = DateTime.Now) { }

// 2void WithStringEmpty(string s = String.Empty) { }

// 3void WithEmptyString(string s = "") { }

// 4void WithMethodCall(int id = GetInvalidId()) { } static int GetInvalidId() { return -1; }

Page 23: Deep Dive  C# by Sergey Teplyakov

Примернаяреализация// Декларация void WithEmptyString(string s = "") { }// Вызов WithEmptyString();

// Декларация [Optional, DefaultParameterValue(value: "")] static void WithEmptyString(string s) {} // Вызов string defaultValue = GetDefaultValueFromMetadata(); WithEmptyString(defaultValue);

Page 24: Deep Dive  C# by Sergey Teplyakov

Можнолиизменить?неизменяемое

( иликакможноизменитьreadonly ?)поля

Page 25: Deep Dive  C# by Sergey Teplyakov

!Вотпример• Как поменять C1.X?class C1 { public readonly int X = 42; }

• Рефлекшн. Но это скушно!class C2 { public int X; }

[StructLayout(LayoutKind.Explicit)]class C1Modifier{    [FieldOffset(0)] public C1 C1;            [FieldOffset(0)] public C2 C2;}

var c1 = new C1(); // c1.X == 42

// Можификация одного поля приводит к модификации другого!var c1Modifier = new C1Modifier {C1 = c1};c1Modifier.C2.X = -1;

// c1.X == -1Console.WriteLine(c1.X);

Создаем класс с таким же «расположением»

Создаем «объединение» (union) из двух таких

классов

Page 26: Deep Dive  C# by Sergey Teplyakov

Ковариантностьмассивов• Что такое ковариантность массивов?• Вот пример:object[] o = new string[]{"s1", "s2", "s3"};

• Что в этом опасного?o[0] = new StringBuilder();

• Это требует проверки типа аргумента во время исполнения!

• Почему?• Так было в Java!!

ArrayTypeMismatchException!

Page 27: Deep Dive  C# by Sergey Teplyakov

Какустроенмассив?внутри

Page 28: Deep Dive  C# by Sergey Teplyakov

Внутреннееустройствомассива

Flags, ...

Length (4)

42 (Element 0)

41 (Element 1)

40 (Element 2)

39 (Element 3)

arr[0]

var arr = new int[] {42, 41, 40, 39};

Flags, ...

Length (4)

Type Indentifier (System.String)

1́µ(Element 0)

2́µ(Element 1)

3́µ(Element 2)

4́µ(Element 3)

arr[0]

var arr = new string[] {"1", "2", "3", "4"};

Page 29: Deep Dive  C# by Sergey Teplyakov

Изменяемразмермассива[StructLayout(LayoutKind.Explicit)]class RefArrayLayout{    [FieldOffset(0)]    public int Length;}

[StructLayout(LayoutKind.Explicit)]class ArrayExplorer{    [FieldOffset(0)]     public object[] Array;

    [FieldOffset(0)]     public RefArrayLayout Layout;}

var array = new string[] {"1", "2"};// array.Length = 2var ae = new ArrayExplorer {Array = array};ae.Layout.Length = 42;

// Получаем 42Console.WriteLine(array.Length);

Page 30: Deep Dive  C# by Sergey Teplyakov

(Значимыетипы Value Types)• В чем главная разница между значимыми и ссылочными

типами?

The most relevant fact about value types is not the implementation detail of how they are allocated, but rather the by-design semantic meaning of “value type”, namely that they are always copied “by value”

Eric Lippert

Page 31: Deep Dive  C# by Sergey Teplyakov

Метод GetHashCode• Нужно ли переопределять у структур метод GetHashCode?• Какова реализация этого метода по умолчанию?• Корректна ли она?

Page 32: Deep Dive  C# by Sergey Teplyakov

ПримервстроенногометодаGetHashCodepublic struct KeyValuePair<TKey, TValue>{    public TKey Key;    public TValue Value;}

public struct KeyValuePair{    public static KeyValuePair<TKey, TValue> Create<TKey, TValue>( TKey key, TValue value)    {        return new KeyValuePair<TKey, TValue>()  {  Key = key,  Value = value  };    }}

Используем реализацию Equals и

GetHashCode по умолчанию

Page 33: Deep Dive  C# by Sergey Teplyakov

Тонкостиреализации• KeyValuePair<int, string>:var kvp1 = KeyValuePair.Create(42, "Foo");var kvp2 = KeyValuePair.Create(42, "Boo");Console.WriteLine(kvp1.GetHashCode() == kvp2.GetHashCode());Console.WriteLine(kvp1.Equals(kvp2));

• KeyValuePair<int, int>:var kvp1 = KeyValuePair.Create(42, 42);var kvp2 = KeyValuePair.Create(42, 43);Console.WriteLine(kvp1.GetHashCode() == kvp2.GetHashCode());Console.WriteLine(kvp1.Equals(kvp2));

• Одинаковый ли хэш-коды?• Равны ли объекты?

Page 34: Deep Dive  C# by Sergey Teplyakov

(2)Тонкостиреализации• ValueType.GetHashCode() содержит две реализации:• Быструю и точную:

• Структура не содержит «пробелов» в содержимом структуры и ссылочных типов.

• Используется бинарное представление всего содержимого структуры.

• Быструю и неточную:• Структура содержит «пробелов» в содержимом или ссылочные

типы.• Используется лишь первое поле структуры.

• Подробнее – Why is ValueType.GetHashCode() implemented like it is?

Page 35: Deep Dive  C# by Sergey Teplyakov

!!!!1111Нееееттт

Page 36: Deep Dive  C# by Sergey Teplyakov

?Вопросы

Page 38: Deep Dive  C# by Sergey Teplyakov

More C# Deep Dive on Programming Stuff

• this == null?• Замыкания в языке C#• Перегрузка и наследование• Структуры и конструкторы по умолчанию• О вреде изменяемых значимых типов.• Часть 1• Часть 2

• MVP Summit. День 0. Кэширование делегатов• MVP Summit. День 1. Об эффективности

Page 39: Deep Dive  C# by Sergey Teplyakov

Спасибо за внимание

• Сергей Тепляков, Visual C# MVP• .NET Architect at Luxoft• [email protected]• http://sergeyteplyakov.blogspot.com/