매개 변수화 유형 목록을 인스턴스화하여 제네릭 및 LINQ를 더 잘 활용합니다.

StackOverflow https://stackoverflow.com/questions/2405862

문제

하나 이상의 해시 알고리즘으로 파일을 해시하고 있습니다. 내가 원하는 해시 유형을 매개 변수로 만들려고했을 때, 내가 기대했던 것보다 훨씬 더 지저분 해졌다.

제네릭이나 LINQ를 더 잘 활용할 수있는 기회가 없다고 생각합니다. 또한 유형을보다 구체적인 유형 세트 (해소 로리즘 후손)로 제한하는 대신 유형을 매개 변수로 사용해야하는 것을 좋아하지 않습니다. 유형을 매개 변수로 지정 하고이 방법을 수행하도록합니다. 건설이지만, 호출자의 해소 로리드 인스턴스가 들어 오게된다면 이것은 더 좋아 보일 것입니다.

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-화 버전이 평범한 것보다 낫지 않은지 의심 스럽다. 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를 사용할 수 있습니다. 특히 하나의 라이너에게는 좋습니다.

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

이와 같은 것은 어떻습니까?

    public string ComputeMultipleHashesOnFile<T>(string filename, T hashClassType)
        where T : HashAlgorithm
    {

    }

WHERE 절은 t 매개 변수를 해소 로리즘 유형으로 제한합니다. 따라서 해소 로리즘에서 클래스 상속을 만들고 추상 클래스 멤버를 구현할 수 있습니다.

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();
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top