31 мая – 1 июня в Киеве состоялась конференция HOTCODE 2013.
Сергей Тепляков, эксперт Luxoft Training по .Net, С++ и архитектуре приложений, выступил с докладом «C# Deep Dive».
Тезисы доклада:
«Когда-то в далеком 2002-м году язык C# был прост, как 2 копейки. Но у любого «живого» языка есть одна особенность, приятная и неприятная одновременно — в язык начинают добавляться новые возможности, чтобы наши с вами типовые задачи решались проще и эффективнее. Но с каждой новой возможностью появляются и свои тонкости, незнание которых может лишить столь нужных в нашей жизни конечностей, причем иногда самым изощренным образом. А поскольку язык C# развивается очень динамично, то за время жизни на его просторах появилось много маленьких грабелек, которые мы с вами и научимся обходить ;)».
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. Есть ли проблема?
using (var file = new FileStream("D:1.txt", FileMode.CreateNew)
{
Position = RestoreLastPosition()
})
{}
13. Как устроен Object Initializer?
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// ...
var person = new Person {Name = "Jonh", Age = 42};
var tmp = new Person();
tmp.Name = "Jonh";
tmp.Age = 42;
var person = tmp;
14. Как устроен using?
using (var file = new FileStream("D:1.txt", FileMode.CreateNew))
{}
var file = new FileStream("d:1.txt", FileMode.CreateNew);
try
{}
finally
{
if (file != null)
((IDisposable)file).Dispose();
}
15. Складываем 2 и 2…
var tmpFile = new FileStream("d:1.txt", FileMode.CreateNew);
// Упс! Если мы здесь упадем, то Dispose вызван не будет!
tmpFile.Position = RestoreLastPosition();
var file = tmpFile;
try
{ }
finally
{
if (file != null)
((IDisposable)file).Dispose();
}
16. Потокобезопасная подписка
на событие?
class A
{
public event EventHandler E;
public void Subscribe(EventHandler e)
{
E = E + e;
}
}
var a = new A();
EventHandler handler = (o, e) => Console.WriteLine("Handler");
a.E += handler; // вызываем из потока 1
a.Subscribe(handler); // вызываем из потока 2
Подробнее об этом –
Chris Burrow "Events get a little overhaul in C# 4, Part II"
Является ли такая
подписка «изнутри»
потокобезопасной?
Такой вариант не
безопасен!
17. Как устроены события?
class AImpl
{
private EventHandler __E;
public event EventHandler E
{
add
{
lock (this) { __E += value; }
}
remove
{
lock (this) { __E += value; }
}
}
public void Subscribe(EventHandler e)
{
__E = (EventHandler)Delegate.Combine(__E, e);
}
}
В C# 4.0 используется
lock-free подход.
А вместо «сырого»
Combine для E+= value
вызывается
экземплярный Add!
18. В C# 4.0…
• События C# 4.0 полностью потокобезопасны
• Подписка на событие «изнутри» класса приводит к вызову
Add текущего объекта
// В C# 4.0 решение полностью потокобезопасно!
class A
{
public event EventHandler E;
public void Subscribe(EventHandler e)
{
E += e;
}
public void RaiseE()
{
var handler = E;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
19. Делегаты…
static void SubscribeTo(EventHandler e)
{
e += (s, ea) => Console.WriteLine("Custom handler");
}
EventHandler handler = null;
SubscribeTo(handler);
handler(null, EventArgs.Empty);
Что произойдет при
вызове?
20. Делегаты – неизменяемы…
• Ведь этот трюк все же знают!
class A
{
public event EventHandler E;
public void RaiseE()
{
var handler = E;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
Не на 100% thread-safe в
теории, но thread-safe на
практике!
21. Виртуальные события
class Base
{
public virtual event EventHandler SomeEvent;
public void RaiseSomeEvent()
{
var handler = SomeEvent;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
class Derived : Base
{
public override event EventHandler SomeEvent;
}
Base b = new Derived();
b.SomeEvent += (s, e) =>
Console.WriteLine("Handler");
b.RaiseSomeEvent();
22. struct S1
{
private readonly int X;
public int GetX() { return X; }
}
struct S2
{
public int X;
}
[StructLayout(LayoutKind.Explicit)]
struct S3
{
[FieldOffset(0)]
public S1 S1;
[FieldOffset(0)]
public S2 S2;
}
var s3 = new S3();
s3.S2.X = 42;
Console.WriteLine(s3.S1.GetX());
Readonly. Правда?
X изменить нельзя!
Правда ведь?
Да это же union из С/С++!!!
Получим 42!!
23. class ArrayHacker
{
public int Length;
}
[StructLayout(LayoutKind.Explicit)]
class ArrayHack
{
[FieldOffset(0)]
public ArrayHacker Hacker;
[FieldOffset(0)]
public int[] Array = new int[2];
}
void Main()
{
var hack = new ArrayHack();
Console.WriteLine(hack.Array.Length); // 2
hack.Hacker.Length = 42;
Console.WriteLine(hack.Array.Length); // 42!!
hack.Array.Dump(); // Получаем "мусор"
}
Изменяем размер массива!
Первые 4 байта массива – это
его размер!
24. А стоит ли их вообще
использовать?
• А как же! Ногу, ведь, чем-то нужно отпиливать!
• Есть приложения, а есть библиотеки – это разные миры.
• Понимание внутреннего устройства сведет проблемы к
минимуму!
27. Чего еще почитать?
• Programming Stuff
• C# Tips and Tricks
• Chris Burrows’ Blog
• Eric Lippert’s Blog
• Joe Duffy’s Weblog
• B# .NET BLOG
28. More C# Deep Dive on
Programming Stuff
• this == null?
• Замыкания в языке C#
• Перегрузка и наследование
• Структуры и конструкторы по умолчанию
• О вреде изменяемых значимых типов.
• Часть 1
• Часть 2
• MVP Summit. День 0. Кэширование делегатов
• MVP Summit. День 1. Об эффективности
29. Спасибо за внимание
• Сергей Тепляков, Visual C# MVP
• .NET Architect at Luxoft
• Sergey.Teplyakov@gmail.com
• http://sergeyteplyakov.blogspot.com/
Hinweis der Redaktion
Тут приходит в голову очень пошлая фотка)
// TODO: Сделать пометки, почему ведет себя код именно так! Сказать, что readonly – доступ к ней – это не обращение к переменной, а получение значения! Добавить b.M.Y++, что это тоже не компилится, поскольку b.M - rvalue