Instantiating قائمة بالأنواع المعلمة، مما يجعل الاستخدام أفضل للأردائر وإنطاقات

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

سؤال

أنا أجذب ملف مع خوارزميات تجزئة واحدة أو أكثر. عندما حاولت تحديد أنواع الأشكال التي أريدها، فقد حصلت على الكثير من أنني كنت آمل.

أعتقد أنني أفتقد فرصة لتحقيق استخدام أفضل للأجنبي أو LINQ. أنا لا أحب أيضا أنه لا بد لي من استخدام نوع [] كمعلمة بدلا من الحد منه إلى مجموعة أكثر تحديدا من النوع (أحفاد الحاشدة)، أود تحديد أنواع كمعلمة واترك هذه الطريقة بناء، ولكن ربما هذا سيبدو أفضل إذا كان لدي مثيلات المتصل الجديد من 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-Evied أفضل من غير عادي 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 من نوع Hashalgorithm. لذلك يمكنك إنشاء ورث من الفئة من HAFHALGORITHM وتنفيذ أعضاء الفصل التجريدي:

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