Давайте начнем с устранения проблемы. Ваше требование состоит в том, что вам нужно вычислить несколько разных типов хэшей в одном и том же файле. Предположим на данный момент, что вам не нужно на самом деле создавать экземпляры типов. Начните с функции, в которой они уже созданы:
public IEnumerable<string> GetHashStrings(string fileName,
IEnumerable<HashAlgorithm> algorithms)
{
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithms
.Select(a => a.ComputeHash(fileBytes))
.Select(b => HexStr(b));
}
Это было легко. Если файлы могут быть большими, и вам нужно их потоковое воспроизведение (имея в виду, что это будет намного дороже с точки зрения ввода-вывода, просто дешевле для памяти), вы также можете сделать это, просто немного более многословно:
public IEnumerable<string> GetStreamedHashStrings(string fileName,
IEnumerable<HashAlgorithm> algorithms)
{
using (Stream fileStream = File.OpenRead(fileName))
{
return algorithms
.Select(a => {
fileStream.Position = 0;
return a.ComputeHash(fileStream);
})
.Select(b => HexStr(b));
}
}
Это немного грубовато, и во втором случае весьма сомнительно, что версия с Linq лучше чем обычный цикл foreach
, но эй, мы развлекаемся, верно?
Теперь, когда мы распутали код генерации хеша, сначала создать его экземпляр не так уж и сложно. Опять же, мы начнем с чистого кода - кода, который использует делегаты вместо типов:
public IEnumerable<string> GetHashStrings(string fileName,
params Func<HashAlgorithm>[] algorithmSelectors)
{
if (algorithmSelectors == null)
return Enumerable.Empty<string>();
var algorithms = algorithmSelectors.Select(s => s());
return GetHashStrings(fileName, algorithms);
}
Теперь это намного приятнее, и выгода в том, что он позволяет создавать экземпляры алгоритмов в методе, но не требует этого. Мы можем вызвать это так:
var hashes = GetHashStrings(fileName,
() => new MD5CryptoServiceProvider(),
() => new SHA1CryptoServiceProvider());
Если мы действительно, действительно, отчаянно должны начать с реальных Type
экземпляров, что я бы постарался не делать, потому что это нарушает проверку типов во время компиляции, тогда мы можем сделать это как последний шаг:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
var algorithmSelectors = algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (Func<HashAlgorithm>)(() =>
(HashAlgorithm)Activator.CreateInstance(t)))
.ToArray();
return GetHashStrings(fileName, algorithmSelectors);
}
И это все. Теперь мы можем запустить этот (плохой) код:
var hashes = GetHashStrings(fileName, typeof(MD5CryptoServiceProvider),
typeof(SHA1CryptoServiceProvider));
В конце концов, это кажется большим количеством кода, но это только потому, что мы скомпоновали решение эффективно, чтобы его было легко тестировать и обслуживать. Если бы мы хотели сделать все это в одном выражении Linq, мы могли бы:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (HashAlgorithm)Activator.CreateInstance(t))
.Select(a => a.ComputeHash(fileBytes))
.Select(b => HexStr(b));
}
Это все, что есть на самом деле. Я пропустил делегированный шаг «селектора» в этой окончательной версии, потому что если вы пишете все это как одну функцию, вам не нужен промежуточный шаг; причина, по которой раньше он был отдельной функцией, заключается в том, чтобы обеспечить как можно большую гибкость, сохраняя при этом безопасность типов во время компиляции. Здесь мы как бы выбросили его, чтобы получить выгоду от более короткого кода.
Edit: Я добавлю одну вещь, которая заключается в том, что, хотя этот код выглядит красивее, он фактически пропускает неуправляемые ресурсы, используемые потомками HashAlgorithm
. Вам действительно нужно сделать что-то вроде этого:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (HashAlgorithm)Activator.CreateInstance(t))
.Select(a => {
byte[] result = a.ComputeHash(fileBytes);
a.Dispose();
return result;
})
.Select(b => HexStr(b));
}
И снова мы как бы теряем здесь ясность. Возможно, было бы лучше сначала создать экземпляры, а затем итерировать их, используя foreach
и yield return
строки хеша. Но вы попросили решение Linq, так что вы здесь. ;)