Что бы каждый раз не париться в поисках нужной константы в C++ных хедерах для очередной функции я создал энумератор примерно такого содержания:
public enum WinError : uint
{
NO_ERROR = 0,
ERROR_INVALID_FUNCTION = 1,
ERROR_FILE_NOT_FOUND = 2,
ERROR_PATH_NOT_FOUND = 3,
ERROR_TOO_MANY_OPEN_FILES = 4,
ERROR_ACCESS_DENIED = 5,
// . . .
// и так 1800 раз, всего 64k
}
К тому же это удобно при выводе сообщения, оно автоматически преобразуется из числа в строку, но в этом то и есть на мой взгляд пробрема: будут эти строки грузится в память все сразу или по мере надобности? Т.е. я вызываю, к примеру, Console.WriteLine(WinError.ERROR_INVALID_FUNCTION); и только в этом случае строка "ERROR_INVALID_FUNCTION" грузится в оперативу для вывода на экран, или же она в попадает туда при старте приложения?
Есть альтернатива вместо энумераторов использовать константы, но, IMHO, это не так эстетично и не так легко получать их имена. К стати, микрософтные программеры, в таких случаях используют именно константы, возможно, что именно по этой причине, или нет?
Вывод:
Допустим у меня есть энумератор из 2000 полей, в проге мне понадобится вывести пусть аж с десятк имен, но как видно это никого не волнует, оперативу тарят полным набором. И так пока не заполнится буфер на 100 энумераторов не взирая на размер каждого в отдельности, у кого-то 2 поля, а у кого-то 10000, по заполнению произойдет сброс, и все начнется сначала. АЦЦТОЙ.
Как вариант: в релизных прогах имена ваще не трогать, особенно у больших энумераторов.
Здравствуйте, Shadedsun, Вы писали:
S>К тому же это удобно при выводе сообщения, оно автоматически преобразуется из числа в строку, но в этом то и есть на мой взгляд пробрема: будут эти строки грузится в память все сразу или по мере надобности?
Все эти строки -- часть метаинформации сборки. Как и все имена классов, полей, методов и т.д.
Здравствуйте, Shadedsun, Вы писали:
S>К тому же это удобно при выводе сообщения, оно автоматически преобразуется из числа в строку, но в этом то и есть на мой взгляд пробрема: будут эти строки грузится в память все сразу или по мере надобности? Т.е. я вызываю, к примеру, Console.WriteLine(WinError.ERROR_INVALID_FUNCTION); и только в этом случае строка "ERROR_INVALID_FUNCTION" грузится в оперативу для вывода на экран, или же она в попадает туда при старте приложения?
В описанном варианте, у занчения, переданного в Console.WriteLine будет вызыван метод ToString внутри этого метода.
Mab>Все эти строки -- часть метаинформации сборки. Как и все имена классов, полей, методов и т.д.
Зато я вас понял. Спасибо за быстрый ответ.
Mab>А вот в чем именно проблема, я так и не понял.
Энумераторы позволяют получить строки без исползования рефлекшина, поэтому я подумал что имена всех констант будут грузится в память сразу при старте приложения, в моем случае это могло бы быть 64к ненужных данных, плюс еще аналог для WM_ и т.д. итого под пол-метра. Но раз оно все хранится в метаданных, значит мое беспокойство о нерациональном использовании оперативы было напрасным.
Здравствуйте, Shadedsun, Вы писали:
S>Энумераторы позволяют получить строки без исползования рефлекшина, поэтому я подумал что имена всех констант будут грузится в память сразу при старте приложения, в моем случае это могло бы быть 64к ненужных данных, плюс еще аналог для WM_ и т.д. итого под пол-метра. Но раз оно все хранится в метаданных, значит мое беспокойство о нерациональном использовании оперативы было напрасным.
На самом деле, дополнительный расход памяти будет, тут решать тебе на сколько он будет приемлем. Все дело в том, что в типе enum содержится статистическое поле fieldInfoHash, которое является кешом. Ключами для него являются конкретные типы enum-а, а значением — ассоциативный массив имя поля enum-а и его значение. При вызове метода .ToString(), если этот ассоциативный массив не создан, он создается и заполняется с помощью рефлексии и помещается в кешь.
Количество типов в этом кеше ограничено и составляет 101, далее кешь обнуляется. Вот специально делал как-то тест:
// создадим 102 типа enum-аpublic enum MyEnum0
{
EnumValue1,
EnumValue2,
EnumValue3,
EnumValue4
}
//...public enum MyEnum101
{
EnumValue1,
EnumValue2,
EnumValue3,
EnumValue4
}
class Program
{
static int count1 = 101;
static int count2 = 102;
static void Main(string[] args)
{
GenerateEnums(count2);
Enum[] enums = CreateEnums();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int k = 0; k < 1000; k++) // тесты прогоняем по 1000 раз, чтобы результат был более внушительный =)
{
TestEnums(enums, count1);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int k = 0; k < 1000; k++)
{
TestEnums(enums, count2);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
static void TestEnums(Enum[] enums, int length)
{
for(int i = 0; i < length; i++)
{
string s = enums[i].ToString();
}
}
static Enum[] CreateEnums(int count)
{
Enum[] enums = new Enum[count];
for (int i = 0; i < count; i++)
{
enums[i] = (Enum)Activator.CreateInstance(
Type.GetType("ConsoleApplication14.MyEnum" + i));
}
return enums;
}
}
Результат:
322 мс - для 101-го enum'а
677 мс - для 102-ух
Как видно изменение количества типов перечеслений на ~1% дало вдвое большее время. Но тест, естественно синтетический, близко принимать к сердцу не стоит
Здравствуйте, Vovstehn, Вы писали:
V> GenerateEnums(count2);
На эту строку не обращайте внимание, просто забыл убрать.
Re[4]: С# enum или const?
От:
Аноним
Дата:
22.06.08 23:36
Оценка:
V>Количество типов в этом кеше ограничено и составляет 101, далее кешь обнуляется. Вот специально делал как-то тест:
Да, черт возьми, есть в этом что-то мистическое. Я запускал такую штуку:
static int[] set = {102, 101, 102, 103, 102, 102, 103, 103, 102, 103, 104};
const int repetitions = 1000;
for (var i = 0; i < set.Length; i++)
{
sw.Start();
for (int k = 0; k < repetitions; k++)
{
TestEnums(enums, set[i]);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
}
// . . .static void TestEnums(Enum[] enums, int length)
{
for (int i = 1; i < length; i++)
{
string[] s = Enum.GetNames(enums[i].GetType());
}
}
У меня цикл в TestEnums стартовал с 1 поэтому порог был между 102 — 40-43мс и 103 — 270-280мс, в то время как разница между 101 и 102, 103 и 104 вообще не была заметна.
S>Энумераторы позволяют получить строки без исползования рефлекшина, поэтому я подумал что имена всех констант будут грузится в память сразу при старте приложения, в моем случае это могло бы быть 64к ненужных данных, плюс еще аналог для WM_ и т.д. итого под пол-метра. Но раз оно все хранится в метаданных, значит мое беспокойство о нерациональном использовании оперативы было напрасным.
Не напрасным, при первой же попытке получить строку из значения enum в память будут загружены все строковые значение этого типа, и эта операция далеко не самая быстрая.
А зачем вообще это надо?
Для ветвления по if согласно моей субъективной практике было достаточно около десятка ошибок виндов, а для остального есть уже готовый механизм на базе Win32Exception, который сам достаёт сообщение по коду.
Так же приличное кол-во виндовых ошибок уже определено в enum SocketError.
Здравствуйте, vdimas, Вы писали: V>Не напрасным, при первой же попытке получить строку из значения enum в память будут загружены все строковые значение этого типа, и эта операция далеко не самая быстрая.
Меня успокоили, что строки в метаданных, я подумал, что напрасно. Потом Vovstehn начал копать глубже и я тож стал, тогда и обнаружилась эта склоннасть загружать все подряд при попытке получить хотя бы одну строку.
V>А зачем вообще это надо? V>Для ветвления по if согласно моей субъективной практике было достаточно около десятка ошибок виндов, а для остального есть уже готовый механизм на базе Win32Exception, который сам достаёт сообщение по коду.
Да, согласен, что достаточно десятка ошибок, но у меня есть енумератор со всеми ошибками, я переделал сишный хедер регулярными выражениям, так че в одном случае выгребать из него один десяток, в другом другой?
Win32Exception, насколько я понял, можно применить если ошибка в .NET функции и тогда можно получить API'шное сообщение и др. инфу по ошибке.
Пример из MSDN:
try {
System.Diagnostics.Process myProc = new System.Diagnostics.Process();
myProc.StartInfo.FileName = "c:\nonexist.exe"; //Attempting to start a non-existing executable
myProc.Start(); //Start the application and assign it to the process component.
}
catch(Win32Exception w) {
Console.WriteLine(w.Message);
Console.WriteLine(w.ErrorCode.ToString());
Console.WriteLine(w.NativeErrorCode.ToString());
Console.WriteLine(w.StackTrace);
Console.WriteLine(w.Source);
Exception e=w.GetBaseException();
Console.WriteLine(e.Message);
}
А если работаешь напрямую с API-функциями?
V>Так же приличное кол-во виндовых ошибок уже определено в enum SocketError.
На счет этого не знал. Буду смотреть.
Здравствуйте, Shadedsun, Вы писали:
S>Да, согласен, что достаточно десятка ошибок, но у меня есть енумератор со всеми ошибками, я переделал сишный хедер регулярными выражениям, так че в одном случае выгребать из него один десяток, в другом другой?
Ну..., сделай один файл и положи себе на видное место, а для реальной приложухи надёргаешь необходимых кодов. Просто я уверен, что и в десятке надобности не будет, ибо обычно логика обработки ошибок на исключениях строится, а не на if-ах. И тебе редко надо в программе узнать причину ошибки, например, при записи файла, обычно достаточно вразумительного сообщения юзверю, и тут как раз уместен Win32Exception, а не мифическое имя константы из enum.
S>А если работаешь напрямую с API-функциями?
А если работаешь напрямую с API-шными ф-иями, то просто используй конструктор от кода возврата или же пустой конструктор, если согласно доки надо взять значение из GetLastError(). Для работы с кодами HRESULT имеется метод Marshal.GetExceptionForHR(int errorCode).
Здравствуйте, vdimas, Вы писали:
V>Ну..., сделай один файл и положи себе на видное место, а для реальной приложухи надёргаешь необходимых кодов. Просто я уверен, что и в десятке надобности не будет, ибо обычно логика обработки ошибок на исключениях строится, а не на if-ах. И тебе редко надо в программе узнать причину ошибки, например, при записи файла, обычно достаточно вразумительного сообщения юзверю, и тут как раз уместен Win32Exception, а не мифическое имя константы из enum.
Вроде внял. Теперь делаю так:
// на низком уровне:class DirectoryReader
{
// . . . public void Start()
{
// . . .
IntPtr hFindFile = FindFirstFile(path, findData);
if
(
hFindFile.ToInt32() == -1 &&
(error = Marshal.GetLastWin32Error()) != (int)WinError.ERROR_FILE_NOT_FOUND
) throw new Win32Exception(error);
// . . .
}
// . . .
}
// на уровень выше:public static void Main2()
{
var dirReader = new DirectoryReader(@"c:\windows\*", DisplayDirectoryItems, 10);
try { dirReader.Start(); }
catch (Win32Exception Win32Error)
{
Console.WriteLine("Error code: {0}\r\n{1}", Win32Error.NativeErrorCode, Win32Error.Message);
}
}
V>А если работаешь напрямую с API-шными ф-иями, то просто используй конструктор от кода возврата или же пустой конструктор, если согласно доки надо взять значение из GetLastError(). Для работы с кодами HRESULT имеется метод Marshal.GetExceptionForHR(int errorCode).
Просмотрел статью "How to: Map HRESULTs and Exceptions", насколько я понял Marshal.GetExceptionForHR() используется для оберток над комами, т.е. их неуправляемые функции возвращают HRESULT, а класс обертка преобразует его в соответствующее исключение.