Некоторым студентам так понравилось ставить точки-с-занятой после конца строки, что они подошли к вопросу с излишним фанатизмом.
В результате получаются такие конструкции:
if (a == b);
{
// выполнится не зависимо от условия
}
Или так:
for (int i=0; i<10; i++);
{
// выполнится один раз, без цикла
}
Ну и со скобками в блоках конечно тоже беда:
for (int i=0; i<10; i++)
a++;
b++;
К сожалению, выравнивания еще мало - надо бы еще скобки блока поставить. Иначе b++ в цикл не войдет.
Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts
Sunday, October 2, 2011
Особенности сравнения float.NaN
float.NaN - специальное обозначение бесконечности. Но нужно помнить, что две бесконечности не равны по определению. Да и по спецификации тоже.
Т.е.
float f = float.NaN;
if (f == float.NaN)
{
// никогда не выполнится
}
Для правильного сравнения используйте float.IsNaN(f).
Т.е.
float f = float.NaN;
if (f == float.NaN)
{
// никогда не выполнится
}
Для правильного сравнения используйте float.IsNaN(f).
Monday, February 21, 2011
Еще пример про делегаты
Задача такая: по имени функции (математической) получить результат ее вычисления. Самый простой вариант такой:
public class CallFunc1
{
/// <summary>
/// Вызываем функцию по имени
/// </summary>
public double GetFuncValue(string funcName, double arg)
{
switch (funcName.ToUpper())
{
case "SIN": return Math.Sin(arg);
case "COS": return Math.Cos(arg);
case "TAN": return Math.Tan(arg);
case "SQRT": return Math.Sqrt(arg);
}
throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
}
}
Но тут мы вспоминаем, что квадратный корень нельзя вычислять от отрицательного значения и добавляем еще код:
case "SQRT":
if (arg < 0)
throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
return Math.Sqrt(arg);
Потом появляются еще функции и свои проверки… В результате метод GetFuncValue напоминает сборную солянку. А хотелось бы как-то попроще и покрасивее сделать.
С помощью делегатов можно сделать так. Описываем делегат, имеющий сигнатуру метода с одним параметром:
// Делегат- математический метод
public delegate double MathFunction(double arg);
А теперь описываем словарь соответствия названий и делегатов таких методов:
// Список функций
private Dictionary<string, MathFunction> funcList;
Инициализировать его нужно будет в конструкторе:
/// <summary>
/// Конструктор
/// </summary>
public CallFunc1()
{
// Создаем список функций
funcList = new Dictionary<string, MathFunction>();
funcList.Add("SIN", delegate(double arg) {
return Math.Sin(arg);
});
funcList.Add("COS", delegate(double arg)
{
return Math.Cos(arg);
});
funcList.Add("TAN", delegate(double arg)
{
return Math.Tan(arg);
});
funcList.Add("SQRT", delegate(double arg)
{
if (arg < 0)
throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
return Math.Sqrt(arg);
});
}
Обратите внимание, что реализация делегата сделана прямо “по месту” (это называется анонимными методами – методами без имени).
А с помощью LINQ вызов станет совсем простым делом:
public double GetFuncValue(string funcName, double arg)
{
// Ищем нужную функцию в списке
var func = funcList.Where(f => f.Key == funcName).Select(f => f.Value).FirstOrDefault();
// Не нашли - генерируем исключение
if (func == null)
throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
// Нашли - вызываем эту функцию
return func(arg);
}
Теперь каждое имя функции связанно со своей реализацией и своими ограничениями.
Делегаты
В C# нет указателей (про unsafe я молчу). И это хорошо и правильно – указатель он по сути своей не типизирован. Передали в метод ссылку, а как ее интерпретировать – на совести принимающей стороны. Можем передать int, а использовать как float. И т.д. Тоже самое касается и методов – передали указатель на метод с одной сигнатурой, а используем совсем по-другому…
Зато в C# есть делегаты. Я бы назвал делегаты “типизированные указатели на методы”. Покажу на примере как это работает.
Сначала нужно описать тип делегата, т.е. сигнатуру метода. Описание можно не привязывать к конкретному классу:
namespace DelegateTest
{
public delegate decimal GetVarValue(string name);
Здесь метод имеет один параметр типа string и возвращает decimal.
Теперь этот делегат мы можем использовать при описании параметров и переменных:
public class Parser
{
/// <summary>
/// Описание переменной, сохращающей делегат
/// </summary>
private GetVarValue getVarValueHandler;
/// <summary>
/// Передаем делегат в конструктор
/// </summary>
/// <param name="getVarValueHandler"></param>
public Parser(GetVarValue getVarValueHandler)
{
// Сохраняем делегат, т.к. хотим использовать его
// в другом методе этого класса
this.getVarValueHandler = getVarValueHandler;
}
/// <summary>
/// Метод расчета
/// </summary>
/// <returns></returns>
public decimal Calc()
{
decimal result = 0;
// Вызываем делегат с параметром var1
result += getVarValueHandler("var1");
// Вызываем делегат с параметром var2
result += getVarValueHandler("var2");
return result;
}
Описание переменной типа делегат выглядит совершенно так же как и обычной переменной. Переменная getVarValueHandler имеет тип GetVarValue, т.е. тип делегата с нашей сигнатурой. Отличие от переменной в том, что мы можем вызывать этот метод. Разумеется, вызывать его можно только так, как он описан, т.е. с одним параметром типа string:
getVarValueHandler("var1");
Прелесть в том, что никак иначе вызвать метод по этой ссылке (а передали мы фактически ссылку на метод) нельзя. Только так, как метод описан в описании делегата.
Использовать этот класс тоже очень просто. Во-первых, нам потребуется метод, который мы будем передавать в конструктор нашего класса. Наверное, излишне говорить, что сигнатура этого метода должна совпадать с сигнатурой делегата и передать в конструктор что-то другое нельзя:
class Program
{
static void Main(string[] args)
{
// Передаем в конструктор делегат, т.е. ссылку
// на наш метод
Parser parser = new Parser(GetVarValueMethod);
// Вызываем метод Calc
Console.WriteLine(parser.Calc());
}
/// <summary>
/// Делегат. Имя у него может быть любое, главное
/// чтобы сигнатура была той как описана в типе
/// делегата
/// </summary>
static decimal GetVarValueMethod(string name)
{
Console.WriteLine("Value of {0}:", name);
return decimal.Parse(Console.ReadLine());
}
}
Имя метода может быть может быть любое, главное чтобы сигнатура совпадала с сигнатурой делегата.
Вот и все. Теперь при вызове метода Calc будет два раза вызван метод GetVarValueMethod.
Saturday, December 4, 2010
Работа с битовыми полями
В C# для работы с битовыми полями предусмотрен класс BitVector32. Этот класс более эффективен, чем BitArray.
using System;
using System.Collections.Specialized;
namespace BitVector32Test
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
BitVector32 vector1 = new BitVector32( 0 );
// Маски для первых пяти битов
int bit1 = BitVector32.CreateMask();
int bit2 = BitVector32.CreateMask(bit1);
int bit3 = BitVector32.CreateMask(bit2);
int bit4 = BitVector32.CreateMask(bit3);
int bit5 = BitVector32.CreateMask(bit4);
// Получаем BitVector32{00000000000000000000000000000000}
Console.WriteLine(vector1.ToString());
vector1[bit1] = true; // установить 1 бит
// Получаем BitVector32{00000000000000000000000000000001}
Console.WriteLine(vector1.ToString());
vector1[bit3] = true; // установить 3 бит
// Получаем BitVector32{00000000000000000000000000000101}
Console.WriteLine(vector1.ToString());
vector1[bit5] = true; // установить 5 бит
// Получаем BitVector32{00000000000000000000000000010101}
Console.WriteLine(vector1.ToString());
BitVector32 vector2 = new BitVector32( 0 );
// Создаем 4 секции. Первая имеет максимум
// значений 6, т. е занимает 4 бита. Вторая - максимум 3
// (т. е. два бита), затем 1 бит, затем 4 бита.
BitVector32.Section sect1 = BitVector32.CreateSection( 6 );
BitVector32.Section sect2 =
BitVector32.CreateSection( 3, sect1 );
BitVector32.Section sect3 =
BitVector32.CreateSection( 1, sect2 );
BitVector32.Section sect4 =
BitVector32.CreateSection(15, sect3 );
vector2[sect2] = 3;
// Получим BitVector32{00000000000000000000000000011000}
Console.WriteLine(vector2.ToString());
vector2[sect4] = 1;
// Получим BitVector32{00000000000000000000000001011000}
Console.WriteLine(vector2.ToString());
}
}
}
using System;
using System.Collections.Specialized;
namespace BitVector32Test
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
BitVector32 vector1 = new BitVector32( 0 );
// Маски для первых пяти битов
int bit1 = BitVector32.CreateMask();
int bit2 = BitVector32.CreateMask(bit1);
int bit3 = BitVector32.CreateMask(bit2);
int bit4 = BitVector32.CreateMask(bit3);
int bit5 = BitVector32.CreateMask(bit4);
// Получаем BitVector32{00000000000000000000000000000000}
Console.WriteLine(vector1.ToString());
vector1[bit1] = true; // установить 1 бит
// Получаем BitVector32{00000000000000000000000000000001}
Console.WriteLine(vector1.ToString());
vector1[bit3] = true; // установить 3 бит
// Получаем BitVector32{00000000000000000000000000000101}
Console.WriteLine(vector1.ToString());
vector1[bit5] = true; // установить 5 бит
// Получаем BitVector32{00000000000000000000000000010101}
Console.WriteLine(vector1.ToString());
BitVector32 vector2 = new BitVector32( 0 );
// Создаем 4 секции. Первая имеет максимум
// значений 6, т. е занимает 4 бита. Вторая - максимум 3
// (т. е. два бита), затем 1 бит, затем 4 бита.
BitVector32.Section sect1 = BitVector32.CreateSection( 6 );
BitVector32.Section sect2 =
BitVector32.CreateSection( 3, sect1 );
BitVector32.Section sect3 =
BitVector32.CreateSection( 1, sect2 );
BitVector32.Section sect4 =
BitVector32.CreateSection(15, sect3 );
vector2[sect2] = 3;
// Получим BitVector32{00000000000000000000000000011000}
Console.WriteLine(vector2.ToString());
vector2[sect4] = 1;
// Получим BitVector32{00000000000000000000000001011000}
Console.WriteLine(vector2.ToString());
}
}
}
Friday, December 3, 2010
Thread-safe словарь
В .Net до версии 3 приходилось словари, доступные для работы в потоках, делать руками. В версии 4 появился удобный класс ConcurrentDictionary. Он находится в пространстве имен System.Collections.Concurrent. Правда к его методам нужно привыкнуть. Например, вместо простого Add здесь bool TryAdd(), который вернет false, если такой ключ уже есть. Аналогично - TryRemove, TryGetValue, TryUpdate.
Обновить значения в hashtable в цикле
Если попробовать изменять значения hashtable во время перебора элементов в цикле, то будет сгенерировано исключение.
Обойти это можно так:
foreach (object key in new List<object>(dataTable.Keys.Cast<object>()))
dataTable[key] = null;
Обойти это можно так:
foreach (object key in new List<object>(dataTable.Keys.Cast<object>()))
dataTable[key] = null;
Wednesday, December 1, 2010
Какая разница между оператором as и прямым приведением типа
В C# существует два способа приведения типов: оператор as и прямое приведение типа. Отличие этих способов том, что при невозможности прямого приведения типов будет сгенерировано исключение InvalidCastException, тогда как оператор as просто вернет null.
Вполне вероятно, что после получения нулевого указателя от оператора as, где-то в коде будет сгенерировано исключение NullReferenceException (если получение null не предусматривалось логикой программы). Т. о. использование прямого приведения предпочтительнее, т. к. найти ошибку в этом случае значительно проще, чем искать безымянный NullReferenceException.
Вполне вероятно, что после получения нулевого указателя от оператора as, где-то в коде будет сгенерировано исключение NullReferenceException (если получение null не предусматривалось логикой программы). Т. о. использование прямого приведения предпочтительнее, т. к. найти ошибку в этом случае значительно проще, чем искать безымянный NullReferenceException.
Tuesday, November 23, 2010
Подключение Dictionary к ComboBox
Простой способ подключить словарь к выпадающему списку без циклов и т.д. Все просто:
cbCommand.DataSource = new BindingSource(myDictionary);"Value";"Key";
cbCommand.DisplayMember =
cbCommand.ValueMember =
Замечу, что подключить его напрямую нельзя, т.к. Dictionary не реализует IList.
cbCommand.DataSource = new BindingSource(myDictionary);"Value";"Key";
cbCommand.DisplayMember =
cbCommand.ValueMember =
Замечу, что подключить его напрямую нельзя, т.к. Dictionary не реализует IList.
Monday, November 22, 2010
В чем разница между typeof и GetType
Разницу можно увидеть с помощью такого кода:
class Base { }
class Derived : Base { }
class Program
{
static void Main()
{
ShowType( new Derived() );
}
static void ShowType( Base b )
{
Console.WriteLine(typeof(Base));
Console.WriteLine(b.GetType());
}
}
Будет напечатано:
Base
Derived
class Base { }
class Derived : Base { }
class Program
{
static void Main()
{
ShowType( new Derived() );
}
static void ShowType( Base b )
{
Console.WriteLine(typeof(Base));
Console.WriteLine(b.GetType());
}
}
Будет напечатано:
Base
Derived
Проверка символа
Если нужно проверить не равен ли символ каким-то конкретным значениям, то не пишите код:
if ((ch=='a') || (ch=='b') || (ch=='c')...)
Есть более простой и красивый путь сделать это:
if ("abc".IndexOf(ch) > 0)
if ((ch=='a') || (ch=='b') || (ch=='c')...)
Есть более простой и красивый путь сделать это:
if ("abc".IndexOf(ch) > 0)
Получение имени файла из полного пути
Иногда в коде я вижу странные методы для получения имени файла из полного пути. Например так:
string[] folders = fileUpload.FileName.Split(new char[] {'/', '\\'});
fileName = folders[folders.Length - 1];
На самом деле все гораздо проще - можно просто написать
Path.GetFileName(fileUpload.FileName).
string[] folders = fileUpload.FileName.Split(new char[] {'/', '\\'});
fileName = folders[folders.Length - 1];
На самом деле все гораздо проще - можно просто написать
Path.GetFileName(fileUpload.FileName).
Разбор URL
Для разбора URL на части использовать методы Replace, IndexOf и т.д. - не правильный подход. Например, удаление префикса:
url.Replace("http://", "") // так не правильно!
Почему? Как минимум, это не будет работать на https-протоколе.
Правильный путь - использовать класс UriBuilder:
namespace UriParse
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
UriBuilder parser = new UriBuilder("http://microsoft.com:80/default.aspx?id=55");
Console.WriteLine(parser.Host); // microsoft.com
Console.WriteLine(parser.Scheme); // http
Console.WriteLine(parser.Uri); // http://microsoft.com/default.aspx?id=55
Console.WriteLine(parser.Path); // /default.aspx
Console.WriteLine(parser.Port); // 80
Console.WriteLine(parser.Query); // ?id=55
}
}
}
url.Replace("http://", "") // так не правильно!
Почему? Как минимум, это не будет работать на https-протоколе.
Правильный путь - использовать класс UriBuilder:
namespace UriParse
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
UriBuilder parser = new UriBuilder("http://microsoft.com:80/default.aspx?id=55");
Console.WriteLine(parser.Host); // microsoft.com
Console.WriteLine(parser.Scheme); // http
Console.WriteLine(parser.Uri); // http://microsoft.com/default.aspx?id=55
Console.WriteLine(parser.Path); // /default.aspx
Console.WriteLine(parser.Port); // 80
Console.WriteLine(parser.Query); // ?id=55
}
}
}
Saturday, November 20, 2010
Не распыляйте логику или информация в Enum
Для перечисления типов отчетов нам нужно было где-то сохранить название отчета и имя файла, хранящего его разметку.
public enum ReportTypes
{
ComponentList = 1,
ComponentAudit = 2,
}
Самый простой вариант – добавить специальные методы, возвращающие по типу соответствующую информацию. Внутри методов, разумеется, торчит switch:
public static string GetReportTitle(ReportTypes type)
{
switch (type)
{
case ReportTypes.ComponentList:
return "Список компонентов";
case ReportTypes.ComponentAudit:
return "Аудит компонентов";
}
return string.Empty;
}
public static string GetReportFileName(ReportTypes type)
{
switch (type)
{
case ReportTypes.ComponentList:
return "ComponentList.frx";
case ReportTypes.ComponentAudit:
return "ComponentAudit.frx";
}
return string.Empty;
}
Первая очевидная проблема в этом коде – если вдруг мы передадим неверный тип отчета, то код об этом не скажет ничего. Исправить это не сложно:
public static string GetReportTitle(ReportTypes type)
{
switch (type)
{
case ReportTypes.ComponentList:
return "Список компонентов";
case ReportTypes.ComponentAudit:
return "Аудит компонентов";
default:
throw new ArgumentException("ReportTypes");
}
}
public static string GetReportFileName(ReportTypes type)
{
switch (type)
{
case ReportTypes.ComponentList:
return "ComponentList.frx";
case ReportTypes.ComponentAudit:
return "ComponentAudit.frx";
default:
throw new ArgumentException("ReportTypes");
}
}
Стало получше, но логика раскидана в трех местах – собственно в перечислении и в двух методах.
Можно попробовать собрать информацию с помощью класса, возвращающего все поля сразу:
public class ReportInfo
{
public string Title { get; set; }
public string FileName { get; set; }
}
public static ReportInfo GetReportInfo(ReportTypes type)
{
...
}
Теперь логика раскидана всего в двух местах, но мне не нравится и это. При добавлении нового типа отчета в перечисление нужно вспоминать, где же лежит метод, возвращающий информацию.
В .NET если очень мощный механизм, позволяющий решить этот вопрос – атрибуты. Создаем атрибут, хранящий информацию об отчете:
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class ReportAttribute : Attribute
{
public ReportAttribute(string title, string fileName)
{
this.ReportTitle = title;
this.ReportFile = fileName;
}
public string ReportTitle
{
get; protected set;
}
public string ReportFile
{
get; protected set;
}
}
Добавляем информацию прямо в перечисление:
public enum ReportTypes
{
[ReportAttribute("Список компонентов", "ComponentList.frx")]
ComponentList = 1,
[ReportAttribute("Аудит компонентов", "ComponentAudit.frx")]
ComponentAudit = 2,
}
Делаем класс, работающий с этими атрибутами:
public static class ReportTypesHelper
{
private static Type type = typeof(ReportTypes);
/// <summary>
/// Атрибут отчета для конкретного типа отчета
/// </summary>
public static ReportAttribute GetReportAttribute(ReportTypes reportType)
{
FieldInfo propInfo = type.GetField(reportType.ToString());
if (propInfo != null)
{
var attribs = propInfo.GetCustomAttributes(typeof(ReportAttribute), false) as ReportAttribute[];
if (attribs != null && attribs.Length > 0)
{
return attribs[0];
}
}
return null;
}
/// <summary>
/// Список отчетов
/// </summary>
/// <returns></returns>
public static IEnumerable<ReportAttribute> GetReportList()
{
var obj = Enum.GetValues(type);
foreach (ReportTypes item in obj)
{
yield return GetReportAttribute(item);
}
}
}
Все. Теперь можно легко получить и список всех отчетов и информацию о конкретном отчете. А все нужная информация хранится в одном месте. Если скорость получения информации критична (в моем случае это было совершенно не принципиально), можно сделать кэш и хранить один раз полученную информацию в нем.
Subscribe to:
Posts (Atom)