سؤال

لدي الكود التالي:

string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

لأن المترجم يستبدل متغير البادئة بإغلاق تتم طباعة "NEW: brownie" على وحدة التحكم.

هل هناك طريقة سهلة لمنع المترجم من رفع متغير البادئة مع الاستمرار في استخدام تعبير لامدا؟أرغب في طريقة لجعل وظيفة Func الخاصة بي تعمل بشكل مماثل لـ:

Func<string, string> prependAction = (x => "OLD:" + x);

سبب حاجتي لذلك هو أنني أرغب في إجراء تسلسل للمفوض الناتج.إذا كان متغير البادئة في فئة غير قابلة للتسلسل، فلن يتم إجراء تسلسل للوظيفة المذكورة أعلاه.

الطريقة الوحيدة للتغلب على هذا الأمر التي يمكنني رؤيتها في الوقت الحالي هي إنشاء فئة جديدة قابلة للتسلسل تقوم بتخزين السلسلة كمتغير عضو ولها طريقة الإضافة المسبقة للسلسلة:

string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));

مع فئة المساعد:

[Serializable]
public class Prepender
{
    public string Prefix { get; set; }
    public string Prepend(string str)
    {
        return Prefix + str;
    }
}

يبدو أن هذا يتطلب الكثير من العمل الإضافي لجعل المترجم "غبيًا".

هل كانت مفيدة؟

المحلول

أرى المشكلة الأساسية الآن.إنه أعمق مما اعتقدت في البداية.الحل الأساسي هو تعديل شجرة التعبير قبل إجراء تسلسل لها، عن طريق استبدال جميع الأشجار الفرعية التي لا تعتمد على المعلمات بعقد ثابتة.يبدو أن هذا يسمى "funkletization".هناك تفسير لذلك هنا.

نصائح أخرى

فقط قم بإغلاق آخر ...

قل شيئًا مثل:

var prepend = "OLD:";

Func<string, Func<string, string>> makePrepender = x => y => (x + y);
Func<string, string> oldPrepend = makePrepender(prepend);

prepend = "NEW:";

Console.WriteLine(oldPrepend("Brownie"));

لم أختبره بعد لأنني لا أستطيع الوصول إلى VS في الوقت الحالي، ولكن عادةً، هذه هي الطريقة التي أحل بها مثل هذه المشكلة.

"تمتص" Lambdas تلقائيًا المتغيرات المحلية، وأخشى أن هذه هي الطريقة التي تعمل بها بحكم التعريف.

هذه مشكلة شائعة جدًا، على سبيل المثال.يتم تعديل المتغيرات عن طريق الإغلاق عن غير قصد - الحل الأبسط بكثير هو اتباع الخطوات التالية:

string prefix = "OLD:";
var actionPrefix = prefix;
Func<string, string> prependAction = (x => actionPrefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

إذا كنت تستخدم resharper، فسيقوم بالفعل بتحديد الأماكن في التعليمات البرمجية الخاصة بك حيث تكون معرضًا لخطر التسبب في آثار جانبية غير متوقعة مثل هذه - لذا إذا كان الملف "أخضر بالكامل"، فيجب أن يكون الرمز الخاص بك على ما يرام.

أعتقد أنه كان من الجيد في بعض النواحي لو كان لدينا بعض السكر النحوي للتعامل مع هذا الموقف حتى نتمكن من كتابته في سطر واحد، على سبيل المثال.

Func<string, string> prependAction = (x => ~prefix + x);

حيث قد يتسبب بعض مشغلي البادئة في تقييم قيمة المتغير قبل إنشاء المفوض/الوظيفة المجهولة.

فهمت المشكلة الآن:يشير لامدا إلى الفئة التي تحتوي على والتي قد لا تكون قابلة للتسلسل.ثم افعل شيئًا مثل هذا:

public void static Func<string, string> MakePrependAction(String prefix){
    return (x => prefix + x);
}

(لاحظ الكلمة الأساسية الثابتة.) إذًا لا تحتاج لامدا إلى الإشارة إلى الفئة التي تحتوي عليها.

توجد بالفعل عدة إجابات هنا تشرح كيف يمكنك تجنب "رفع" لامدا للمتغير الخاص بك.لسوء الحظ، هذا لا يحل مشكلتك الأساسية.إن عدم القدرة على إجراء تسلسل لامدا ليس له علاقة بـ "رفع" لامدا المتغير الخاص بك.إذا كان تعبير لامدا يحتاج إلى مثيل لفئة غير تسلسلية لحسابه، فمن المنطقي تمامًا أنه لا يمكن إجراء تسلسل له.

اعتمادًا على ما تحاول فعله بالفعل (لا أستطيع أن أقرر تمامًا من منشورك)، سيكون الحل هو نقل الجزء غير القابل للتسلسل من لامدا إلى الخارج.

على سبيل المثال، بدلاً من:

NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);

يستخدم:

NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);

ماذا عن هذا

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

ماذا عن:

string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

?

حسنًا، إذا كنا سنتحدث عن "المشاكل" هنا، فإن لامدا تأتي من عالم البرمجة الوظيفية، وفي لغة برمجة وظيفية بحتة، لا توجد مهام وبالتالي لن تنشأ مشكلتك أبدًا لأن قيمة البادئة لا يمكن أن تتغير أبدًا.أتفهم أن C# يعتقد أنه من الرائع استيراد الأفكار من البرامج الوظيفية (لأن FP يكون رائعة!) ولكن من الصعب جدًا جعلها جميلة، لأن لغة #C هي لغة برمجة ضرورية وستظل دائمًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top