3. Настолько глубоко?
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!!!!
4. Нет! Нет! Нет!
• Подробнее об этом треше -
http://rsdn.ru/forum/dotnet/3272728.flat
• См. этюды nikov-а на rsdn.ru –
http://rsdn.ru/Forum/?fuid=55905
• Кури “The C# Programming Language” by Hejlsberg et al!
5. Что нужно для работы цикла
foreach?
• IEnumerable?
• IEnumerable of T?
• Что-то еще?
• Нужен метод GetEnumerator, возвращающий
объект с методом MoveNext и свойством
Current!
6. В F# пошли еще дальше…
• Поддержку цикла for можно добавить с помощью методов
расширения!
type Int32 with
// Получаем список квадратов чисел от 1 до текущего значения
member x.GetEnumerator() =
({1..x} |> Seq.map(fun x -> x*x)).GetEnumerator()
// Выводит 1 4 9 16 25
for n in 5 do printf "%d " n
7. Утиная типизация
Если кто-то ходит, как утка, и крякает, как
утка, то это и есть может быть утка индюшка
с утиным адаптером...
8. «Утиная типизация» в C#
• foreach
• Требуется GetEnumerator, MoveNext и свойство Current
• http://sergeyteplyakov.blogspot.com/2012/08/duck-typing-
foreach.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
• Методы расширения завязаны не на конкретный тип
атрибутов!
9. Блоки итераторов…
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); // 1
var s = seq.Select(line => line.Length); // 2
Console.WriteLine(s.Max()); // 3
Все ли нормально с
кодом?
Этот же подход
используется и для
асинхронных методов!
Когда получим
исключение?
Код до первого yield
return вызовется при
первом вызове метода
MoveNext!
10. Какое исключение получим?
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>();
Какое исключение
получим?
11. Почему?
• Используется reflection (Activator.CreateInstance).
• Все обобщения должны содержать одну реализацию!
• Вызов метода через Reflection всегда «оборачивает»
исходное исключение в TargetInvocationException
• «Все нетривиальные абстракции текут»
Джоэл Спольски
12. Повторная генерация
исключений
• Делаем "правильную" фабрику. Наивная реализация:
public static T CreateInstanceNaive<T>() where T : new()
{
try
{
return new T();
}
catch (TargetInvocationException e)
{
// Исходный стек вызовов потерян, теперь все
// будут думать, что виноваты мы!
throw e.InnerException;
}
}
13. Используем ExceptionDispatchInfo
public 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);
}
}
14. 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"?
15. Quiz # 4. Маскирование
исключений в блоке using
class CustomDisposable : IDisposable
{
public void Dispose()
{
throw new InvalidOperationException("Ooops!!");
}
}
// Какое исключение перехватывать?
using (var disposable = new CustomDisposable())
{
throw new FileNotFoundException();
}
16. Подходы к потерянным
исключениям
• C# - побеждает исключение из finally. Исходное теряем
• С++
• Исключение в деструкторе – UB ;)
• std::unexpected, если деструктор вызван при раскрутке стека
• Java
• Исключение в блоке try-finally
• Тоже самое, что и в C#
• try-with-resources
• Побеждает исключение из finally, но исходное остается в скрытых
исключениях
17. 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!!!
}
}
Что будет в этом
случае?
18. Обработка ошибок…
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); // 1
var s = seq.Select(line => line.Length); // 2
Console.WriteLine(s.Max()); // 3
Все ли нормально с
кодом?
Этот же подход
используется и для
асинхронных методов!
Когда получим
исключение?
Код до первого yield
return вызовется при
первом вызове метода
MoveNext!
19. Корректная реализация
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); // 1
var s = seq.Select(line => line.Length); // 2
Console.WriteLine(s.Max()); // 3
20. В чем разница?
• Исключение при вызове метода – ошибка в вызывающем
коде
• Исключение во время перебора элементов –
ошибка/проблема в вызываемом коде!
21. Необязательные аргументы
и полиморфизм
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!
22. Что будет в этом случае?
// 1
void WithDateTime(DateTime dt = DateTime.Now) { }
// 2
void WithStringEmpty(string s = String.Empty) { }
// 3
void WithEmptyString(string s = "") { }
// 4
void WithMethodCall(int id = GetInvalidId()) { }
static int GetInvalidId() { return -1; }
25. Вот пример!
• Как поменять 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 == -1
Console.WriteLine(c1.X);
Создаем класс с таким же
«расположением»
Создаем «объединение»
(union) из двух таких
классов
26. Ковариантность массивов
• Что такое ковариантность массивов?
• Вот пример:
object[] o = new string[]{"s1", "s2", "s3"};
• Что в этом опасного?
o[0] = new StringBuilder();
• Это требует проверки типа аргумента во время
исполнения!
• Почему?
• Так было в Java!!
ArrayTypeMismatchException!
28. Внутреннее устройство массива
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"};
29. Изменяем размер массива
[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 = 2
var ae = new ArrayExplorer {Array = array};
ae.Layout.Length = 42;
// Получаем 42
Console.WriteLine(array.Length);
30. Значимые типы (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
31. Метод GetHashCode
• Нужно ли переопределять у структур метод GetHashCode?
• Какова реализация этого метода по умолчанию?
• Корректна ли она?
32. Пример встроенного метода
GetHashCode
public 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 по
умолчанию
33. Тонкости реализации
• 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));
• Одинаковый ли хэш-коды?
• Равны ли объекты?
34. Тонкости реализации (2)
• ValueType.GetHashCode() содержит две реализации:
• Быструю и точную:
• Структура не содержит «пробелов» в содержимом структуры и
ссылочных типов.
• Используется бинарное представление всего содержимого
структуры.
• Быструю и неточную:
• Структура содержит «пробелов» в содержимом или ссылочные
типы.
• Используется лишь первое поле структуры.
• Подробнее – Why is ValueType.GetHashCode() implemented
like it is?
37. Чего еще почитать?
• Programming Stuff
• C# Tips and Tricks
• Chris Burrows’ Blog
• Eric Lippert’s Blog
• Joe Duffy’s Weblog
• B# .NET BLOG
38. More C# Deep Dive on
Programming Stuff
• this == null?
• Замыкания в языке C#
• Перегрузка и наследование
• Структуры и конструкторы по умолчанию
• О вреде изменяемых значимых типов.
• Часть 1
• Часть 2
• MVP Summit. День 0. Кэширование делегатов
• MVP Summit. День 1. Об эффективности
39. Спасибо за внимание
• Сергей Тепляков, Visual C# MVP
• .NET Architect at Luxoft
• Sergey.Teplyakov@gmail.com
• http://sergeyteplyakov.blogspot.com/
Editor's Notes
Тут приходит в голову очень пошлая фотка)
Обязательно сказать, что traceability – очень важная составляющая любого приложения.Поэтому если вы где-либо логируете лишь ex.Message, то писать вам всегда на языке С! (это ведь страшнее, чем гореть в аду в страшных муках).
Получим "3".Сказать, что это важно, поскольку эта же самая проблема, но в несколько завуалированном виде возникает достаточно часто!
Применимо в том же WCF-е! Когда Dispose приводит к закрытию клиента в результате чего исходное исключение теряется!
У жабы в try-with-resources – самый четкий подход!!!Сказать, что у этой проблемы нет простого решения!
Да, тут можно сказать, что в С++\CLI все будет зашибись! Как и в старом С++!
Продолжаем тему обработки исключений.
И теперь мы «упадем» в первой строке!
TODO: Рисунок с контрактами!
Это не сверх полезная информация, тем не менее, у нее есть и некоторые любопытные особенности.
Но прежде чем переходить к
Объекты не равны в обоих случаях, но в первом случае методы GetHashCode возвращают один и тот же результат!
Посмотрите заметку по ссылке, чтобы понять, о чем идет речь!
// TODO: Сделать пометки, почему ведет себя код именно так! Сказать, что readonly – доступ к ней – это не обращение к переменной, а получение значения! Добавить b.M.Y++, что это тоже не компилится, поскольку b.M - rvalue