Создание экземпляра списка параметризованных типов, более эффективное использование Generics и Linq

StackOverflow https://stackoverflow.com/questions/2405862

Вопрос

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

Я думаю, что я упускаю шанс лучше использовать дженерики или LINQ.Мне также не нравится, что я должен использовать тип[] в качестве параметра вместо того, чтобы ограничивать его более конкретным набором типов (потомки HashAlgorithm), я бы хотел указать типы в качестве параметра и позволить этому методу выполнять построение, но, возможно, это выглядело бы лучше, если бы у меня были вызывающие новые экземпляры HashAlgorithm для передачи?

public List<string> ComputeMultipleHashesOnFile(string filename, Type[] hashClassTypes)
        {
            var hashClassInstances = new List<HashAlgorithm>();
            var cryptoStreams = new List<CryptoStream>();

            FileStream fs = File.OpenRead(filename);
            Stream cryptoStream = fs;

            foreach (var hashClassType in hashClassTypes)
            {
                object obj = Activator.CreateInstance(hashClassType);
                var cs = new CryptoStream(cryptoStream, (HashAlgorithm)obj, CryptoStreamMode.Read);

                hashClassInstances.Add((HashAlgorithm)obj);
                cryptoStreams.Add(cs);

                cryptoStream = cs;
            }

            CryptoStream cs1 = cryptoStreams.Last();

            byte[] scratch = new byte[1 << 16];
            int bytesRead;
            do { bytesRead = cs1.Read(scratch, 0, scratch.Length); }
            while (bytesRead > 0);

            foreach (var stream in cryptoStreams)
            {
                stream.Close();
            }

            foreach (var hashClassInstance in hashClassInstances)
            {
                Console.WriteLine("{0} hash = {1}", hashClassInstance.ToString(), HexStr(hashClassInstance.Hash).ToLower());
            }
        }
Это было полезно?

Решение

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

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);
}

Теперь это намного приятнее, и преимущество в том, что это позволяет создание экземпляров алгоритмов внутри метода, но не требовать IT.Мы можем вызвать его следующим образом:

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));
}

Вот и все, что в этом действительно есть.Я пропустил делегированный шаг "селектор" в этой окончательной версии, потому что, если вы пишете все это как одну функцию, вам не нужен промежуточный шаг;причина того, что она была выделена в качестве отдельной функции ранее, заключалась в том, чтобы обеспечить как можно большую гибкость при сохранении безопасности типов во время компиляции.Здесь мы как бы отбросили это, чтобы воспользоваться преимуществами более сжатого кода.


Редактировать:Я добавлю одну вещь, которая заключается в том, что, хотя этот код выглядит красивее, на самом деле он пропускает неуправляемые ресурсы, используемые 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, так что вот оно.;)

Другие советы

Почему вы предоставляете типы как Types и создавать их, а не просто разрешать пользователю передавать экземпляры HashAlgorithm?Похоже, что это полностью решило бы проблему.

Если это требование, то то, что у вас есть, действительно единственное решение, поскольку вы не можете указать переменное количество параметров типа для универсального типа или функции (что, похоже, вам понадобится, поскольку теперь это массив), и вы не можете принудительно использовать передаваемые типы для определенной строки наследования (так же, как вы не можете принудительно использовать целочисленный параметр в диапазоне от 1 до 10).Такого рода проверка может быть выполнена только во время выполнения.

Просто незначительный момент здесь, ничего новаторского.Всякий раз, когда вы выполняете предварительный поиск по списку, вы можете использовать linq.Это особенно приятно для одноразовых лайнеров:

cryptoStreams.ForEach(s => s.Close());
hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);

Как насчет чего-то подобного этому?

    public string ComputeMultipleHashesOnFile<T>(string filename, T hashClassType)
        where T : HashAlgorithm
    {

    }

Предложение where ограничивает параметр T типом HashAlgorithm.Таким образом, вы можете создать класс, наследующий от HashAlgorithm, и реализовать члены абстрактного класса:

public class HA : HashAlgorithm
{
    protected override void HashCore(byte[] array, int ibStart, int cbSize)
    {
        throw new NotImplementedException();
    }

    protected override byte[] HashFinal()
    {
        throw new NotImplementedException();
    }

    public override void Initialize()
    {
        throw new NotImplementedException();
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top