Создание списка параметризованных типов, более эффективное использование Generics и Linq - PullRequest
3 голосов
/ 09 марта 2010

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

Я думаю, что упускаю шанс лучше использовать дженерики или LINQ. Мне также не нравится, что я должен использовать Type [] в качестве параметра вместо того, чтобы ограничивать его более конкретным набором типов (потомки 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());
            }
        }

Ответы [ 4 ]

4 голосов
/ 09 марта 2010

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

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

1 голос
/ 09 марта 2010

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

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, так что вы здесь. ;)

1 голос
/ 09 марта 2010

Как насчет этого?

    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();
    }
}
1 голос
/ 09 марта 2010

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

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