Вложено с помощью операторов в C#
Я работаю над проектом. Я должен сравнить содержимое двух файлов и посмотреть, точно ли они соответствуют друг другу.
Перед многими проверками и проверками ошибок мой первый черновик:
DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\");
FileInfo[] files = di.GetFiles(filename + ".*");
FileInfo outputFile = files.Where(f => f.Extension == ".out").Single<FileInfo>();
FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single <FileInfo>();
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
{
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
{
return false;
}
}
return (outFile.EndOfStream && expFile.EndOfStream);
}
}
Кажется немного странным иметь вложенность using
заявления.
Есть лучший способ сделать это?
16 ответов
Предпочтительный способ сделать это - поставить только открывающую скобку {
после последнего using
утверждение, как это:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
///...
}
Если объекты одного типа, вы можете сделать следующее
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
expFile = new StreamReader(expectedFile.OpenRead()))
{
// ...
}
Когда IDisposable
одного типа, вы можете сделать следующее:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
expFile = new StreamReader(expectedFile.OpenRead()) {
// ...
}
Страница MSDN на using
имеет документацию по этой языковой функции.
Вы можете сделать следующее независимо от того, IDisposable
с того же типа:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamWriter anotherFile = new StreamReader(anotherFile.OpenRead()))
{
// ...
}
Начиная с C# 8.0 вы можете использовать объявление using.
using var outFile = new StreamReader(outputFile.OpenRead());
using var expFile = new StreamReader(expectedFile.OpenRead());
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
{
return false;
}
}
return (outFile.EndOfStream && expFile.EndOfStream);
Это избавит от используемых переменных в конце области видимости переменных, то есть в конце метода.
Если вы не возражаете объявить переменные для вашего блока using перед блоком using, вы можете объявить их все в одном выражении using.
Test t;
Blah u;
using (IDisposable x = (t = new Test()), y = (u = new Blah())) {
// whatever...
}
Таким образом, x и y - это просто переменные-заполнители типа ID, доступные для использования блоком using, и вы используете t и u внутри своего кода. Просто подумал, что упомяну.
Оператор using работает с интерфейсом IDisposable, поэтому другим вариантом может быть создание составного класса некоторого типа, который реализует IDisposable и имеет ссылки на все объекты IDisposable, которые вы обычно помещаете в оператор using. Обратной стороной этого является то, что вы должны сначала объявить свои переменные и выходить за пределы области видимости, чтобы они были полезны в блоке using, требуя большего количества строк кода, чем требовалось бы для некоторых других предложений.
Connection c = new ...;
Transaction t = new ...;
using (new DisposableCollection(c, t))
{
...
}
В этом случае конструктор для DisposableCollection является массивом params, поэтому вы можете вводить сколько угодно.
Если вы хотите сравнить файлы эффективно, вообще не используйте StreamReaders, и тогда в использовании нет необходимости - вы можете использовать низкоуровневое чтение потока, чтобы получить буферы данных для сравнения.
Вы также можете сначала сравнить такие вещи, как размер файла, чтобы быстро обнаружить различные файлы, чтобы избавить себя от необходимости читать все данные.
Вы можете сгруппировать несколько одноразовых объектов в один оператор использования с запятыми:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
expFile = new StreamReader(expectedFile.OpenRead()))
{
}
Вы также можете сказать:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
...
}
Но некоторые люди могут найти это трудно читать. Кстати, как оптимизация вашей проблемы, почему вы не проверяете, чтобы размеры файлов были одинакового размера в первую очередь, прежде чем переходить строка за строкой?
Также, если вы уже знаете пути, нет смысла сканировать каталог.
Вместо этого я бы порекомендовал что-то вроде этого:
string directory = Path.Combine(Environment.CurrentDirectory, @"TestArea\");
using (StreamReader outFile = File.OpenText(directory + filename + ".out"))
using (StreamReader expFile = File.OpenText(directory + filename + ".exp")))
{
//...
Path.Combine
добавит папку или имя файла к пути и удостоверится, что между путем и именем есть только одна обратная косая черта.
File.OpenText
откроет файл и создаст StreamReader
на одном дыхании.
Приставляя строку к @, вы можете избежать экранирования от обратной косой черты (например, @"a\b\c"
)
Вы можете опустить скобки на всех, кроме самых внутренних, используя:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
{
return false;
}
}
}
Я думаю, что это чище, чем помещать несколько одинаковых типов в одно и то же использование, как предлагали другие, но я уверен, что многие люди сочтут это непонятным
И чтобы добавить ясности, в этом случае, поскольку каждый последующий оператор является одним оператором (а не блоком), вы можете опустить все квадратные скобки:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
while (!(outFile.EndOfStream || expFile.EndOfStream))
if (outFile.ReadLine() != expFile.ReadLine())
return false;
В этом нет ничего странного. using
это сокращенный способ обеспечения удаления объекта после завершения блока кода. Если у вас есть одноразовый предмет во внешнем блоке, который должен использовать внутренний блок, это вполне приемлемо.
Редактировать: Слишком медленный набор текста, чтобы показать пример консолидированного кода. +1 всем остальным.
Вы также спрашиваете, есть ли лучший способ сравнить с файлами? Я предпочитаю рассчитывать CRC или MD5 для обоих файлов и сравнивать их.
Например, вы можете использовать следующий метод расширения:
public static class ByteArrayExtender
{
static ushort[] CRC16_TABLE = {
0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };
public static ushort CalculateCRC16(this byte[] source)
{
ushort crc = 0;
for (int i = 0; i < source.Length; i++)
{
crc = (ushort)((crc >> 8) ^ CRC16_TABLE[(crc ^ (ushort)source[i]) & 0xFF]);
}
return crc;
}
Как только вы это сделаете, сравните файлы довольно просто:
public bool filesAreEqual(string outFile, string expFile)
{
var outFileBytes = File.ReadAllBytes(outFile);
var expFileBytes = File.ReadAllBytes(expFile);
return (outFileBytes.CalculateCRC16() == expFileBytes.CalculateCRC16());
}
Вы можете использовать встроенный класс System.Security.Cryptography.MD5, но вычисляемый хеш является байтом [], поэтому вам все равно придется сравнивать эти два массива.
Это нормальный способ использования и работает идеально. Хотя есть и другие способы реализации этого. Почти каждый ответ уже присутствует в ответе на этот вопрос. Но здесь я перечисляю их всех вместе.
Уже использован
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
{
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
return false;
}
}
}
Опция 1
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
return false;
}
}
}
Вариант 2
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
expFile = new StreamReader(expectedFile.OpenRead()))
{
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
if (outFile.ReadLine() != expFile.ReadLine())
return false;
}
}
Я думаю, что, возможно, нашел синтаксически более чистый способ объявления этого выражения с помощью, и мне кажется, это работает? использование var в качестве типа в операторе using вместо IDisposable, по-видимому, динамически выводит тип для обоих объектов и позволяет мне создавать экземпляры обоих объектов и вызывать их свойства и методы класса, которому они выделены, как в using(var uow = new UnitOfWorkType1(), uow2 = new UnitOfWorkType2()){}.
Если кто-то знает, почему это не так, пожалуйста, дайте мне знать
Они появляются время от времени, когда я тоже пишу код. Вы могли бы рассмотреть перемещение второго оператора using в другую функцию?