deep dive c# by sergey teplyakov
DESCRIPTION
TRANSCRIPT
Deep DiveСергей Тепляков, Visual C# MVP
.NET Architect at LuxoftSergeyTeplyakov.blogspot.com
Анасколько глубоко ?будемнырять
?Настолько глубоко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!!!!
! ! !Нет Нет Нет• Подробнее об этом треше -
http://rsdn.ru/forum/dotnet/3272728.flat• См. этюды nikov-а на rsdn.ru –
http://rsdn.ru/Forum/?fuid=55905• Кури “The C# Programming Language” by Hejlsberg et al!
Чтонужнодляработы цикла foreach?
• IEnumerable?• IEnumerable of T?• Что-то еще?
• Нужен метод GetEnumerator, возвращающий объект с методом MoveNext и свойством Current!
В 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
Утинаятипизация
Если кто-то ходит, как утка, и крякает, как утка, то это и есть может быть утка индюшка с утиным адаптером...
« » Утинаятипизация в 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• Методы расширения завязаны не на конкретный тип
атрибутов!
…Блокиитераторов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!
Какоеисключение?получим
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>();
Какое исключение получим?
?Почему• Используется reflection (Activator.CreateInstance). • Все обобщения должны содержать одну реализацию!• Вызов метода через Reflection всегда «оборачивает»
исходное исключение в TargetInvocationException
• «Все нетривиальные абстракции текут» Джоэл Спольски
Повторная генерацияисключений• Делаем "правильную" фабрику. Наивная реализация:
public static T CreateInstanceNaive<T>() where T : new(){ try { return new T(); } catch (TargetInvocationException e) { // Исходный стек вызовов потерян, теперь все // будут думать, что виноваты мы! throw e.InnerException; }}
Используем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); }}
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"?
Quiz # 4. Маскирование исключенийвблоке using
class CustomDisposable : IDisposable{ public void Dispose() { throw new InvalidOperationException("Ooops!!"); }}
// Какое исключение перехватывать?using (var disposable = new CustomDisposable()){ throw new FileNotFoundException();}
Подходыкпотеряннымисключениям• C# - побеждает исключение из finally. Исходное теряем• С++• Исключение в деструкторе – UB ;)• std::unexpected, если деструктор вызван при раскрутке стека
• Java• Исключение в блоке try-finally
• Тоже самое, что и в C#• try-with-resources
• Побеждает исключение из finally, но исходное остается в скрытых исключениях
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!!! }}
Что будет в этом случае?
…Обработкаошибок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!
Корректнаяреализация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
?Вчемразница• Исключение при вызове метода – ошибка в вызывающем
коде• Исключение во время перебора элементов –
ошибка/проблема в вызываемом коде!
Необязательные аргументыи
полиморфизм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!
?Чтобудет в этомслучае// 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; }
Примернаяреализация// Декларация void WithEmptyString(string s = "") { }// Вызов WithEmptyString();
// Декларация [Optional, DefaultParameterValue(value: "")] static void WithEmptyString(string s) {} // Вызов string defaultValue = GetDefaultValueFromMetadata(); WithEmptyString(defaultValue);
Можнолиизменить?неизменяемое
( иликакможноизменитьreadonly ?)поля
!Вотпример• Как поменять 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) из двух таких
классов
Ковариантностьмассивов• Что такое ковариантность массивов?• Вот пример:object[] o = new string[]{"s1", "s2", "s3"};
• Что в этом опасного?o[0] = new StringBuilder();
• Это требует проверки типа аргумента во время исполнения!
• Почему?• Так было в Java!!
ArrayTypeMismatchException!
Какустроенмассив?внутри
Внутреннееустройствомассива
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"};
Изменяемразмермассива[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);
(Значимыетипы 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
Метод GetHashCode• Нужно ли переопределять у структур метод GetHashCode?• Какова реализация этого метода по умолчанию?• Корректна ли она?
Примервстроенногометода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 по умолчанию
Тонкостиреализации• 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));
• Одинаковый ли хэш-коды?• Равны ли объекты?
(2)Тонкостиреализации• ValueType.GetHashCode() содержит две реализации:• Быструю и точную:
• Структура не содержит «пробелов» в содержимом структуры и ссылочных типов.
• Используется бинарное представление всего содержимого структуры.
• Быструю и неточную:• Структура содержит «пробелов» в содержимом или ссылочные
типы.• Используется лишь первое поле структуры.
• Подробнее – Why is ValueType.GetHashCode() implemented like it is?
!!!!1111Нееееттт
?Вопросы
?Чегоещепочитать• Programming Stuff• C# Tips and Tricks
• Chris Burrows’ Blog• Eric Lippert’s Blog• Joe Duffy’s Weblog• B# .NET BLOG
More C# Deep Dive on Programming Stuff
• this == null?• Замыкания в языке C#• Перегрузка и наследование• Структуры и конструкторы по умолчанию• О вреде изменяемых значимых типов.• Часть 1• Часть 2
• MVP Summit. День 0. Кэширование делегатов• MVP Summit. День 1. Об эффективности
Спасибо за внимание
• Сергей Тепляков, Visual C# MVP• .NET Architect at Luxoft• [email protected]• http://sergeyteplyakov.blogspot.com/