2010-03-09 2 views
3

하나 이상의 해시 알고리즘으로 파일을 해시합니다. 내가 원하는 해시 타입을 매개 변수화하려고 시도했을 때, 기대했던 것보다 훨씬 복잡해졌습니다.Generics 및 Linq를 더 잘 사용하여 매개 변수화 된 유형의 목록 인스턴스화

제네릭이나 LINQ를 더 잘 활용할 수있는 기회를 놓치고 있다고 생각합니다. 또한 유형 ([HashAlgorithm descendants])의보다 구체적인 집합으로 제한하는 대신 매개 변수로 Type []을 사용해야한다는 점이 마음에 들지만 매개 변수로 형식을 지정하고이 메서드가 생성,하지만 어쩌면 이것은 내가 호출자에게 전달할 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()); 
      } 
     } 

답변

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

그것의 약간 울퉁불퉁하고 두 번째 경우에, 바로 높은 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)); 
} 

다시 말해서 여기에 명확한 부분이 없어졌습니다. 인스턴스를 먼저 생성 한 다음 foreachyield return 해시 문자열을 사용하여 인스턴스를 반복하는 것이 좋습니다. 하지만 Linq 솔루션을 요청 했으므로 거기에 있습니다. ;)

+0

이런 식으로'Select' 문을 연결하는 특별한 이유가 있습니까? 동일한 블록 내에서 인스턴스를 인스턴스화하고, 사용하고, 그 다음에 'Dispose'할 수없는 이유는 없습니다. –

+0

@ Adam Robinson : 가독성, 주로. 그것은 Select 문을 연결하거나 많은 메서드 호출을 묶는 것이며, 이전의 형식은 SO의 좁은 코드 블록에서 더 좋네요. 필자가 몇 번 언급했듯이, 내가이 글을 쓰고 있다면 아마 하나의'foreach' 루프를 사용할 것입니다.하지만 그는 Linq에게 물어 봤습니다. 그래서 그것이 제가 제공 한 것입니다. : P – Aaronaught

+0

@Aaron : 내가 말하고자하는 것은 두 개의 select 문이 기본적으로 코드를 변경하지 않고 하나로 결합 될 수 있다는 것입니다. –

4

Types으로 유형을 공급하고이를 만드는보다는 사용자가 HashAlgorithm의 경우에 통과 할 수 있도록? 문제를 완전히 완화시키는 것처럼 보입니다.

요구 사항 인 경우 제네릭 유형 또는 함수에 가변 개수의 유형 매개 변수를 지정할 수 없기 때문에 현재 가지고있는 솔루션이 실제로 유일한 솔루션입니다. 이제 배열), 전달 된 유형을 특정 상속 행으로 적용 할 수 없습니다 (정수 매개 변수를 1에서 10 사이의 값으로 지정할 수있는 것 이상). 이러한 종류의 유효성 검사는 런타임에만 수행 할 수 있습니다.

1

여기 사소한 점은 아무 것도 없습니다. 당신이 목록을 foreach 때마다 linq 사용할 수 있습니다. 하나의 라이너에 특히 좋습니다 :

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

또 다른 사소한 점으로 LINQ와는 아무런 관련이 없습니다. 'List '클래스는'ForEach' 메소드를 제공합니다. 이것은'System.Linq' 네임 스페이스의 일부가 아닙니다. –

+2

또 다른 부수적 인 점은, 만약 여러분이'ForEach' 확장 메서드를'IEnumerable '에 직접 작성해도, 가독성 측면에서 어떤 개선점도 보이지 않지만 * 약간의 성능 저하 및 프로그램 테스트하거나 디버그하는 것이 훨씬 더 어렵습니다. – Aaronaught

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(); 
    } 
} 
+0

이것은 다중 알고리즘을 허용하지 않습니다. 그의 원래 코드는'Type'을위한 배열을 사용했습니다. –

+0

그럼, 다른 방법 ([string, HashAlgorithm]의 배열을 처리하는)을 호출하십시오. – CRice

관련 문제