Domanda

Sono hashing un file con uno o più algoritmi di hash. Quando ho provato a parametrizzare i tipi di hash che voglio, ha ottenuto un Messier molto di quello che speravo.

Credo che mi manca la possibilità di fare un uso migliore dei farmaci generici o LINQ. Anche io non piace che devo usare un tipo [] come parametro invece di limitarla a un insieme più specifica di tipo (discendenti HashAlgorithm), vorrei specificare i tipi come il parametro e lasciare che questo metodo di fare il costruire, ma forse questo sarebbe un aspetto migliore se avessi avuto il chiamante nuovi-up istanze di HashAlgorithm per passare a?

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());
            }
        }
È stato utile?

Soluzione

Cominciamo rompendo il problema verso il basso. Il vostro requisito è che è necessario calcolare diversi tipi di hash sullo stesso file. Si supponga per il momento che si non bisogno di istanziare in realtà i tipi. Inizia con una funzione che li ha già un'istanza:

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

E 'stato facile. Se i file potrebbero essere di grandi dimensioni ed è necessario lo streaming (tenendo presente che questo sarà molto più costoso in termini di I / O, appena più conveniente per la memoria), si può fare anche questo, è solo un po 'più dettagliato:

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

E 'un po' nodoso e nel secondo caso è altamente discutibile o meno la versione Linq-ified è meglio di un ciclo foreach ordinario, ma hey, ci stiamo divertendo, giusto?

Ora che abbiamo districato il codice hash generazione, li istanziare prima in realtà non è molto più difficile. Anche in questo caso inizieremo con il codice che è pulito - il codice che utilizza i delegati invece di tipi:

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

Ora, questo è molto più bello, e il vantaggio è che permette esemplificazione degli algoritmi all'interno del metodo, ma non lo fa richiede di esso. Siamo in grado di richiamare in questo modo:

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

Se davvero, davvero, disperatamente bisogna partire dalle istanze Type effettivi, che mi piacerebbe provare di non farlo perché rompe a tempo di compilazione controllo del tipo, allora possiamo farlo come il ultimo passaggio:

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

E questo è tutto. Ora siamo in grado di eseguire questo codice (cattivo):

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

Alla fine della giornata, questo mi sembra più codice, ma è solo perché abbiamo composto la soluzione in modo efficace in un modo che è facile da testare e mantenere. Se volessimo fare tutto questo in una sola espressione LINQ, si potrebbe:

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

Questo è tutto davvero è ad esso. Ho saltato la fase di "selettore" delega in questa versione finale, perché se si sta scrivendo tutto questo come una funzione che non è necessario il passaggio intermedio; la ragione per avere come una funzione separata prima è quella di dare la massima flessibilità possibile, pur mantenendo compilazione tempo di sicurezza tipo. Qui abbiamo sorta di gettato via per ottenere il beneficio di codice di terser.


Edit: Vorrei aggiungere una cosa, e cioè che anche se questo codice sembra più bella, in realtà perde le risorse non gestite utilizzate dai discendenti HashAlgorithm. Hai davvero bisogno di fare qualcosa di simile, invece:

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

E ancora stiamo tipo di perdere la chiarezza qui. Potrebbe essere meglio per costruire solo le istanze, poi scorrere attraverso di loro con foreach e yield return le corde di hash. Ma lei ha chiesto una soluzione LINQ, quindi non ci sei. ;)

Altri suggerimenti

Perché stai fornendo i tipi come Types e la creazione di loro, piuttosto che solo permettendo all'utente di passare in casi di HashAlgorithm? Sembra che sarebbe alleviare il problema del tutto.

Se questo è un requisito, allora quello che hai è davvero l'unica soluzione, dal momento che non è possibile specificare un numero variabile di parametri di tipo su un tipo generico o la funzione (che sembra che avresti bisogno, dal momento che si tratta di un allineamento ora), e non è possibile applicare i tipi passati in essere di una particolare linea di successione (non più di quanto si può imporre che un parametro intero compreso tra 1 e 10). Questo genere di convalida può essere fatto solo in fase di esecuzione.

Solo un punto minore qui, rottura niente a terra. Ogni volta che si foreach nel corso di un elenco che è possibile utilizzare LINQ. E 'particolarmente bello per uno liners:

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

Che dire qualcosa di simile?

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

    }

La clausola WHERE limita il parametro T essere di tipo HashAlgorithm. Così si può creare una classe che eredita da HashAlgorithm e implementare i membri della classe astratta:

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();
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top