3

1 つ以上のハッシュ アルゴリズムを使用してファイルをハッシュしています。必要なハッシュ タイプをパラメータ化しようとすると、思っていたよりもかなり複雑になりました。

ジェネリックや 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 に答える 4

4

Typesユーザーが のインスタンスを渡すだけでなく、タイプを as として提供し、それらを作成するのはなぜHashAlgorithmですか? それは問題を完全に軽減するようです。

これが要件である場合、ジェネリック型または関数で可変数の型パラメーターを指定できないため、実際には唯一の解決策です (現在は配列であるため、必要になるようです)。であり、渡された型を特定の継承行に強制することはできません (整数パラメーターが 1 から 10 の間であることを強制する以上)。この種の検証は、実行時にのみ実行できます。

于 2010-03-09T00:39:17.420 に答える
1

問題を分解することから始めましょう。あなたの要件は、同じファイルでいくつかの異なる種類のハッシュを計算する必要があるということです。差し当たり、型を実際にインスタンス化する必要はないと仮定してください。それらがすでにインスタンス化されている関数から始めます。

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

それは簡単でした。ファイルが大きく、それをストリーミングする必要がある場合 (これは I/O の点ではるかにコストがかかり、メモリのコストが安くなることを覚えておいてください)、それも行うことができますが、それはもう少し冗長です:

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

それは少し危険で、2 番目のケースでは、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));

結局のところ、これはより多くのコードのように見えますが、それは、テストと保守が容易な方法でソリューションを効果的に構成したためです。これをすべて 1 つの 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));
}

それだけです。この最終バージョンでは、委任された「セレクター」ステップをスキップしました。これをすべて 1 つの関数として記述している場合、中間ステップは必要ないからです。以前に別の関数として使用した理由は、コンパイル時の型の安全性を維持しながら、可能な限り多くの柔軟性を提供するためです。ここでは、より簡潔なコードの利点を得るために、それを捨てました。


編集: 1 つ追加します。このコードは見栄えがよくなりますが、実際には、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 ソリューションを求めたので、そこにあります。;)

于 2010-03-09T01:39:18.110 に答える
1

ここではマイナーな点であり、画期的なことは何もありません。リストを foreach するときはいつでも、linq を使用できます。これは、1 つのライナーに特に適しています。

cryptoStreams.ForEach(s => s.Close());
hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);
于 2010-03-09T00:45:05.637 に答える
1

このようなものはどうですか?

    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();
    }
}
于 2010-03-09T00:55:40.273 に答える