Форум программистов, компьютерный форум, киберфорум
el_programmer
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
PVS-Studio - это инструмент для выявления ошибок в исходном коде программ, написанных на языках С, C++ и C#.

PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.

Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
Оценить эту запись

Занимательный C#

Запись от el_programmer размещена 15.06.2016 в 14:52
Обновил(-а) tezaurismosis 19.06.2016 в 16:50 (Рекламные ссылки удалены)

Автор: Виталий Алферов

Для оценки качества диагностик анализатора C# кода PVS-Studio мы проверяем большое количество различных проектов. Т.к. проекты пишутся разными людьми в различных командах в разных компаниях, нам приходится сталкиваться с различными стилями, сокращениями, да и просто возможностями, которые предлагает язык C# программистам. В этой статье я хочу обзорно пройтись по некоторым моментам, которые предлагает нам замечательный язык C#, и по тем проблемам, на которые можно наткнуться при его использовании.

[ATTACH]3880[/ATTACH]

[size=5][b]Свойства и что с ними можно делать[/b][/size]

Мы все знаем, что свойства - это пара функций - аксессор и мутатор - для изменения и чтения значения в каком-то поле. Ну, или по крайней мере так было до версии языка C# 3.0. Т.е. классически они должны выглядеть вот так:

[CSHARP]class A
{
int index;
public int Index
{
get { return index; }
set { index = value; }
}
}
[/CSHARP]

Шли годы, и стандарты языка, и свойства обросли разными возможностями.

Начнем понемногу. В стандарте C# 3.0 появилась всем известная возможность опустить поле, т.е. записать так:

[CSHARP]class A
{
public int Index { get; set; }
}
[/CSHARP]

В C# 6.0 пошли еще дальше и позволили убрать "set".

[CSHARP]class A
{
public int Index { get; }
}
[/CSHARP]

Так писать можно было и до C# 6.0, но записать в такую переменную что-либо было нельзя. Теперь это, по факту, является аналогом [i]readonly [/i]полей, т.е. задавать значение таких свойств можно только в конструкторе.

Свойства и поля можно инициализировать различными способами. Например, так:

[CSHARP]class A
{
public List<int> Numbers { get; } = new List<int>();
}
[/CSHARP]

Ну или так:

[CSHARP]class A
{
public List<int> Numbers = new List<int>();
}
[/CSHARP]

А еще можно написать так:

[CSHARP]class A
{
public List<int> Numbers => new List<int>();
}
[/CSHARP]

И в последнем случае вас будет ждать неприятный сюрприз. На самом деле, в последнем примере вы создали вот такое вот свойство:

[CSHARP]class A
{
public List<int> Numbers { get { return new List<int>(); } }
}
[/CSHARP]

Т.е. когда вы попытаетесь заполнить [i]Numbers[/i], то у вас ничего не получится в принципе, каждый раз вы будете иметь новый список.

[CSHARP]A a = new A();
a.Numbers.Add(10);
a.Numbers.Add(20);
a.Numbers.Add(30);
[/CSHARP]

Будьте внимательны, когда сокращаете запись, иногда это может привести к весьма долгому поиску ошибки.

Интересные свойства свойств на этом не заканчиваются. Как я уже сказал, свойство - это пара функций, а в функциях никто не мешает менять параметры, которые туда приходят.

Следующий код прекрасно компилируется и даже работает.

[CSHARP]class A
{
int index;
public int Index
{
get { return index; }
set {
value = 20;
index = value; }
}
}
static void Main(string[] args)
{
A a = new A();
a.Index = 10;
Console.WriteLine(a.Index);
}
[/CSHARP]

Результатом работы будет вывод числа "20", а никак не "10".

Казалось бы, зачем кому-то сдалось записывать значение 20 в [i]value[/i]? Оказывается, даже в этом может быть смыл. Но для пояснения этого смысла, мы немного отвлекаемся от свойств и расскажем о ключевом символе [i]@[/i]. Данный ключевой символ поваляет создавать переменные схожие по написаю с ключевыми словами, но никто не запрещает данный символ пихать куда душа пожелает, например:

[CSHARP]class A
{
public int index;
public void CopyIndex(A @this)
{
this.@index = @this.index;
}
}
static void Main(string[] args)
{
A a = new A();
@a.@index = 10;
a.@CopyIndex(new A() { @index = 20 });
Console.WriteLine(a.index);
}
[/CSHARP]

Результатом работы, как всегда в этой статье, будет вывод числа "20", а никак не "10".

На самом деле, символ [i]@[/i] необходим только в одном месте, когда пишем имя параметра [i]@this[/i] в функции [i]CopyIndex[/i]. В других местах это просто лишний код, который к тому же затрудняет понимание написанного.

С этими знаниями вернемся к свойствам и предположим, что у нас есть следующий класс:

[CSHARP]class A
{
int value;
public int Value
{
get { return @value; }
set { @value = value; }
}
public A()
{
value = 5;
}
}
[/CSHARP]

Можно подумать, что в свойстве [i]Value [/i]изменит поле [i]value[/i] класса [i]A[/i], на самом деле так не произойдет, и результатом работы следующий программы будет 5, а не 10.

[CSHARP]static void Main(string[] args)
{
A a = new A();
a.Value = 10;
Console.WriteLine(a.Value);
}
[/CSHARP]


[size=5][b]Инициализация Dictionary[/b][/size]

Для начала вспомним, как можно инициализировать массивы:

[CSHARP]string[] test1 = new string[] { "1", "2", "3" };
string[] test2 = new[] { "1", "2", "3" };
string[] test3 = { "1", "2", "3" };
string[,] test4 = { { "11", "12" },
{ "21", "22" },
{ "31", "32" } };
[/CSHARP]

Со списками дело обстоит проще:

[CSHARP]List<string> test2 = new List<string>(){ "1", "2", "3" };
[/CSHARP]

А теперь перейдем в [i]Dictionary[/i]. На самом деле есть два варианта сокращенной инициализации:

[CSHARP]Dictionary<string, int> test =
new Dictionary<string, int>() { { "a-a", 1 },
{ "b-b", 2 },
{ "c-c", 3 } };
[/CSHARP]

Ну или так

[CSHARP]Dictionary<string, int> test =
new Dictionary<string, int>() {
["a-a"] = 1,
["b-b"] = 2,
["c-c"] = 3
};
[/CSHARP]


[size=5][b]Немного о LINQ запросах[/b][/size]

LINQ запросы в принципе сама по себе вещь удобная. Собираем цепочку с необходимыми выборками и на выходе получаем необходимую информацию. Для начала опишем пару приятных моментов, которые могут не прийти в голову, пока сам не увидишь их. Для начала рассмотрим базовый пример:

[CSHARP]void Foo(List<int> numbers1, List<int> numbers2) {
var selection1 = numbers1.Where(index => index > 10);
var selection2 = numbers2.Where(index => index > 10);
}
[/CSHARP]

Нетрудно заметить, что в выше описанном примере есть несколько одинаковых проверок. То есть по-хорошему, их можно вынести в отдельную "функцию":

[CSHARP]void Foo(List<int> numbers1, List<int> numbers2) {
Func<int, bool> whereFunc = index => index > 10;
var selection1 = numbers1.Where(index => whereFunc(index));
var selection2 = numbers2.Where(index => whereFunc(index));
}
[/CSHARP]

Уже стало лучше, если функции большие, то вообще прекрасно. Немного смущает вызов [i]whereFunc[/i]: какой-то он неказистый. На самом деле, это тоже не проблема:

[CSHARP]void Foo(List<int> numbers1, List<int> numbers2) {
Func<int, bool> whereFunc = index => index > 10;
var selection1 = numbers1.Where(whereFunc);
var selection2 = numbers2.Where(whereFunc);
}
[/CSHARP]

Вот теперь и лаконично и опрятно.

Теперь немного о нюансах работы LINQ выражений. Например, строчка кода не приведет к моментальной выборке данных из коллекции [i]numbers1[/i].

[CSHARP]IEnumerable<int> selection = numbers1.Where(whereFunc);
[/CSHARP]

Выборка данных начнется, только когда будет выполнена конвертация последовательности в коллекцию List<int>:

[CSHARP]List<int> listNumbers = selection.ToList();
[/CSHARP]

Этот нюанс работы может легко привести к использованию захваченной переменной уже после того, как её значение изменилось. Возьмем простой пример. Допустим, нам нужна функция [i]Foo,[/i]которая вернет из массива "{ 1, 2, 3, 4, 5 }" только те элементы, численные значения которых меньше индекса элемента, т.е:

[CSHARP]0 :
1 :
2 : 1
3 : 1, 2
4 : 1, 2, 3
[/CSHARP]

Её сигнатура пусть будет такой:

[CSHARP]static Dictionary<int, IEnumerable<int>> Foo(int[] numbers)
{ .... }
[/CSHARP]

А вызов вот такой:

[CSHARP]foreach (KeyValuePair<int, IEnumerable<int>> subArray in
Foo(new[] { 1, 2, 3, 4, 5 }))
Console.WriteLine(string.Format("{0} : {1}",
subArray.Key,
string.Join(", ", subArray.Value)));
[/CSHARP]

Всё вроде бы просто. Теперь напишем саму реализацию на основе LINQ. Она будет выглядеть вот так:

[CSHARP]static Dictionary<int, IEnumerable<int>> Foo(int[] numbers)
{
var result = new Dictionary<int, IEnumerable<int>>();
for (int i = 0; i < numbers.Length; i++)
result[i] = numbers.Where(index => index < i);
return result;
}
[/CSHARP]

Как можно видеть, всё предельно просто. Мы берем и поочерёдно "создаем" выборки из массива [i]numbers[/i].

Результатом работы такой программы будет вот такой текст в консоли:

[CSHARP]0 : 1, 2, 3, 4
1 : 1, 2, 3, 4
2 : 1, 2, 3, 4
3 : 1, 2, 3, 4
4 : 1, 2, 3, 4
[/CSHARP]

Проблема тут как раз в замыкании, которое произошло в лямбде [i]index => index < i[/i]. Переменная [i]i [/i]была захвачена, но, так как вызов лямбда выражения [i]index => index < i [/i]не происходил до момента, когда мы попросили результат в функции [i]string.Join(", ", subArray.Value)[/i], значение в ней было не такое, как в момент формирования LINQ запроса. Во время получения данных из выборки значения [i]i[/i] было равным 5, что привело к неверному результату вывода.


[size=5][b]Недокументированные костыли на C#[/b][/size]

Язык С++ известен своими хаками, обходными путями и прочими костылями, чего стоит серия функций [i]XXX_cast[/i]. Считается, что в C# такого нет. На самом деле и это не совсем правда...

Начнем, пожалуй, с нескольких слов:
[list][*][i]__makeref[/i][*][i]__reftype[/i][*][i]__refvalue[/i][/list]
Этих слов нет ни в IntelliSense, да и в MSDN нет официального описания к ним.

Так что это за чудо-слова такие?

[i]__makeref [/i]принимает объект и возвращает некую "ссылку" на объект в виде объекта типа [i]TypedReference[/i]. А, собственно, слова [i]__reftype[/i] и [i]__refvalue[/i] позволяют из этой "ссылки" узнать соответственно тип объекта и значение объекта по данной "ссылке".

Рассмотрим пример:

[CSHARP]struct A { public int Index { get; set; } }
static void Main(string[] args)
{
A a = new A();
a.Index = 10;
TypedReference reference = __makeref(a);
Type typeRef = __reftype(reference);
Console.WriteLine(typeRef); //=> ConsoleApplication23.Program+A
A valueRef = __refvalue(reference, A);
Console.WriteLine(valueRef.Index); //=> 10
}
[/CSHARP]

Но такой "финт ушами" можно сделать немного более известными средствами:

[CSHARP]static void Main(string[] args)
{
A a = new A();
a.Index = 10;
dynamic dynam = a;
Console.WriteLine(dynam.GetType());
A valuDynam = (A)dynam;
Console.WriteLine(valuDynam.Index);
}
[/CSHARP]

С [i]dynamic[/i] и строк меньше, да и вопросов меньше должно вызывать у людей - "Что это?" и "Как это работает?". Но вот вам немного иной сценарий, где работа с [i]dynamic[/i] смотрится не так хорошо, как с [i]TypedReference[/i].

[CSHARP]static void Main(string[] args)
{
TypedReference reference = __makeref(a);
SetVal(reference);
Console.WriteLine(__refvalue(reference, A).Index);
}
static void SetVal(TypedReference reference)
{
__refvalue(reference, A) = new A() { Index = 20 };
}
[/CSHARP]

Результатом работы будет вывод на консоль числа "20". Да, можно и [i]dynamic [/i]через [i]ref [/i]в функцию передать и работать будет также.

[CSHARP]static void Main(string[] args)
{
dynamic dynam = a;
SetVal(ref dynam);
Console.WriteLine(((A)dynam).Index);
}
static void SetVal(ref dynamic dynam)
{
dynam = new A() { Index = 20 };
}
[/CSHARP]

На мой взгляд, вариант с [i]TypedReference[/i] выглядит лучше, особенно если прокидывать информацию всё ниже, и ниже, и ниже по функциям.

Кроме выше описанных, есть еще одно чудо-слово [i]__arglist,[/i] которое позволяет сделать функцию с переменным числом параметров, да еще и любого типа.

[CSHARP]static void Main(string[] args)
{
Foo(__arglist(1, 2.0, "3", new A[0]));
}
public static void Foo(__arglist)
{
ArgIterator iterator = new ArgIterator(__arglist);
while (iterator.GetRemainingCount() > 0)
{
TypedReference typedReference =
iterator.GetNextArg();
Console.WriteLine("{0} / {1}",
TypedReference.ToObject(typedReference),
TypedReference.GetTargetType(typedReference));
}
}
[/CSHARP]

Странным является то, что нельзя из коробки организовать проход по элементам с помощью [i]foreach[/i], да и напрямую к элементу из списка не обратиться. Так что до С++ или JavaScript c его [i]arguments [/i]не дотягивает.:)

[CSHARP]function sum() {
....
for(var i=0; i < arguments.length; i++)
s += arguments[i]
}
[/CSHARP]

В дополнение приведу ссылку на [url=http://aakinshin.net/ru/blog/dotnet/undocumented-keywords-in-cs/]статью[/url], из которой я, собственно, и узнал, что это за слова такие, когда первый раз с ними столкнулся.


[size=5][b]Заключение [/b][/size]

В заключение хочется сказать, что и С++ и C# - весьма свободные по грамматике языки, и тем самым с одной стороны удобны в использовании, но с другой не защищают от опечаток. Есть укоренившееся мнение, что в С# нельзя ошибаться так, как в С++, – на самом деле это вовсе не так. В данной статье приведены весьма интересные, на мой взгляд, возможности языка, но львиная доля ошибок в C# состоит не в них, а при написании обычных индукций [i]if[/i], как, например, в проекте Infragistics.

[CSHARP]public bool IsValid
{
get {
var valid =
double.IsNaN(Latitude) || double.IsNaN(Latitude) ||
this.Weather.DateTime == Weather.DateTimeInitial;
return valid;
}
}
[/CSHARP]

V3001 There are identical sub-expressions 'double.IsNaN(Latitude)' to the left and to the right of the '||' operator. WeatherStation.cs 25

Внимание рассеивается чаще всего именно в таких моментах, а потом долгие поиски "непонятно чего непонятно где". Так что не упускайте возможность уберечь себя от ошибок с помощью анализатора кода PVS-Studio.
Миниатюры
Нажмите на изображение для увеличения
Название: image1.png
Просмотров: 627
Размер:	55.8 Кб
ID:	3880  
Размещено в Без категории
Показов 2109 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru