Instanciation d'une liste des types paramétrés, une meilleure utilisation des médicaments génériques et LINQ

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

Question

Je hachant un fichier avec un ou plusieurs algorithmes de hachage. Quand j'ai essayé de paramétrer quels types hachage je veux, il a beaucoup messier que j'espérais.

Je pense que je manque une chance de faire un meilleur usage des médicaments génériques ou LINQ. Je ne veux pas aussi comme que je dois utiliser un type [] comme paramètre au lieu de la limiter à un ensemble plus spécifique de type (descendants HashAlgorithm), je voudrais spécifier les types que le paramètre et laisser cette méthode faire la la construction, mais peut-être ce serait mieux si j'avais l'appelant de nouvelles instances de-up HashAlgorithm pour passer en?

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());
            }
        }
Était-ce utile?

La solution

Commençons par briser le problème vers le bas. Votre exigence est que vous devez calculer plusieurs types de hash différents sur le même fichier. On suppose pour le moment que vous ne pas doivent instancier réellement les types. Commencez par une fonction qui les a déjà instancié:

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

C'était facile. Si les fichiers peuvent être importants et que vous devez le diffuser (en gardant à l'esprit que ce sera beaucoup plus cher en termes d'E / S, juste moins cher pour la mémoire), vous pouvez le faire aussi, il est juste un peu plus bavard:

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

Il est un peu noueux et dans le second cas, il est très douteux si oui ou non la version LINQ ified est mieux qu'une boucle foreach ordinaire, mais bon, nous amuser, à droite?

Maintenant que nous avons démêlé le code de génération de hachage, les instanciation d'abord n'est pas vraiment beaucoup plus difficile. Encore une fois, nous allons commencer avec le code qui est propre - le code qui utilise les délégués au lieu de types:

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

Maintenant, ce qui est beaucoup plus agréable, et l'avantage est que permet instanciation des algorithmes dans la méthode, mais ne pas besoin il. Nous pouvons l'invoquer comme ceci:

var hashes = GetHashStrings(fileName,
    () => new MD5CryptoServiceProvider(),
    () => new SHA1CryptoServiceProvider());

Si nous avons vraiment, vraiment, désespérément besoin de commencer par les instances de Type réelles, que je vais essayer de ne pas faire, car il casse la vérification du type de compilation, alors nous pouvons le faire comme dernière étape:

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

Et voilà. Maintenant, nous pouvons exécuter ce (mauvais) Code:

var hashes = GetHashStrings(fileName, typeof(MD5CryptoServiceProvider),
    typeof(SHA1CryptoServiceProvider));

A la fin de la journée, cela semble être plus de code, mais il est seulement parce que nous avons composions la solution efficace d'une manière qui est facile à tester et à entretenir. Si nous voulions faire tout cela en une seule expression Linq, nous pourrions:

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

C'est tout ce qu'il ya vraiment à elle. J'ai sauté le délégué étape « de sélection » dans cette version finale parce que si vous écrivez tout cela comme une fonction que vous n'avez pas besoin de l'étape intermédiaire; la raison de l'avoir comme fonction séparée précédemment est de donner autant de flexibilité que possible tout en maintenant la sécurité de type compilation. Ici, nous avons en quelque sorte jeté loin pour obtenir l'avantage du code terser.


Edit: Je vais ajouter une chose, qui est que, même si ce code semble plus joli, il fuit en fait les ressources non managées utilisées par les descendants de HashAlgorithm. Vous avez vraiment besoin de faire quelque chose comme ceci:

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

Et encore une fois, nous sommes en quelque sorte de perdre la clarté ici. Il pourrait être préférable de simplement construire les instances d'abord, puis itérer à travers eux avec foreach et yield return les chaînes de hachage. Mais vous avez demandé une solution LINQ, donc il vous. ;)

Autres conseils

Pourquoi vous fournissez les types comme Types et les créer plutôt que de simplement permettre à l'utilisateur de passer dans les cas de HashAlgorithm? Il semble que cela atténuer le problème tout à fait.

Si cela est une exigence, alors ce que vous avez est vraiment la seule solution, puisque vous ne pouvez pas spécifier un nombre variable de paramètres type sur un type générique ou la fonction (ce qui semble que vous auriez besoin, car il est un tableau maintenant), et on ne peut pas appliquer les types adoptées pour être d'une ligne de transmission particulière (pas plus que vous pouvez faire appliquer qu'un paramètre entier compris entre 1 et 10). Ce genre de validation ne peut se faire lors de l'exécution.

Juste un point mineur ici, rien de rupture du sol. Chaque fois que vous foreach sur une liste que vous pouvez utiliser LINQ. Il est particulièrement agréable pour un liners:

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

Qu'en est-il quelque chose comme ça?

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

    }

La clause where limite le paramètre T être de type HashAlgorithm. Vous pouvez donc créer une classe héritant de HashAlgorithm et mettre en œuvre les membres de la classe abstraite:

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();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top