ジェネリック医薬品とのLINQをより良く利用して、パラメータ化された型のリストをインスタンス化
-
18-09-2019 - |
質問
私は1つのまたは複数のハッシュアルゴリズムを使用してファイルをハッシングしています。欲しい商品がハッシュタイプのパラメータ化しようとしたとき、それは私が望んでいたよりも多くの乱雑を得ました。
私は、ジェネリックまたはLINQをよりよく活用する機会を逃していると思います。また、私は、パラメータとしてのタイプを指定するには、[]の代わりにタイプ(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());
}
}
解決
の問題を分解することから始めましょう。あなたの要件は、あなたが同じファイルのハッシュのいくつかの異なる種類を計算する必要があるということです。あなたがを一瞬想定は、実際の型をインスタンス化する必要はありません。彼らはすでにインスタンス化している機能を起動します:
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));
}
}
これはちょっと危ないですし、後者の場合には、それはLINQの-ifiedバージョンは通常の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));
}
これは実際にそれにすべてがあります。あなたはすべて一つの機能としてこれを書いている場合は、中間ステップを必要としないので、私は、この最終版で委任「セレクタ」ステップをスキップしました。以前に別の関数としてそれを持つ理由は、まだコンパイル時の型の安全性を維持しながら、できるだけ多くの柔軟性を与えることです。ここでは、ソートのterserコードの利益を得るためにそれを捨ててきます。
<時間>編集:私はこのコードがきれいに見えますが、それは実際に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のソリューションを求めました。 ;)
他のヒント
なぜあなたはTypes
ようなタイプを供給し、それらを作成するだけではなく、ユーザがHashAlgorithm
のインスタンスに渡すことができていますか?それは完全に問題を軽減するだろうようです。
これは必要条件である場合には、その後、何を持っていることは、本当に唯一のソリューションです今アレイ)、あなたは特定の継承ライン(あなたは整数パラメータは1と10の間であることを強制することができるよりもそれ以上)であることが渡されたタイプを強制することができません。検証のその種は、実行時にのみ行うことができます。
ここだけのマイナーポイント、何も画期的。あなたは、LINQを使用することができ、リスト上のforeachたび。これは、1つのライナーには特にうれしいです。
cryptoStreams.ForEach(s => s.Close());
hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);
このようなことについては何?
public string ComputeMultipleHashesOnFile<T>(string filename, T hashClassType)
where T : HashAlgorithm
{
}
句は、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();
}
}