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