문제를 해결하여 시작하겠습니다. 귀하의 요구 사항은 동일한 파일에서 여러 종류의 해시를 계산해야한다는 것입니다. 잠시 동안 이 없다고 가정하면은 실제로 유형을 인스턴스화해야합니다. 이미 인스턴스화 된 함수로 시작하십시오.
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));
}
그게 전부입니다. 이 최종 버전에서는 위임 된 "선택기"단계를 건너 뛰었습니다.이 모든 것을 하나의 함수로 작성한다면 중간 단계가 필요하지 않기 때문입니다. 이전에 별도의 함수로 사용하는 이유는 가능한 한 많은 유연성을 제공하면서도 컴파일 타임 유형 안전을 유지하는 것입니다. 여기서 우리는 더 간결한 코드의 이점을 얻기 위해 그것을 던져 버렸습니다.
편집 :이 코드는 예뻐 보이지만, 그것은 실제로 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 솔루션을 요청 했으므로 거기에 있습니다. ;)
이런 식으로'Select' 문을 연결하는 특별한 이유가 있습니까? 동일한 블록 내에서 인스턴스를 인스턴스화하고, 사용하고, 그 다음에 'Dispose'할 수없는 이유는 없습니다. –
@ Adam Robinson : 가독성, 주로. 그것은 Select 문을 연결하거나 많은 메서드 호출을 묶는 것이며, 이전의 형식은 SO의 좁은 코드 블록에서 더 좋네요. 필자가 몇 번 언급했듯이, 내가이 글을 쓰고 있다면 아마 하나의'foreach' 루프를 사용할 것입니다.하지만 그는 Linq에게 물어 봤습니다. 그래서 그것이 제가 제공 한 것입니다. : P – Aaronaught
@Aaron : 내가 말하고자하는 것은 두 개의 select 문이 기본적으로 코드를 변경하지 않고 하나로 결합 될 수 있다는 것입니다. –