سؤال

هل من الممكن تقييم ما يلي في C# في وقت التشغيل

لدي فئة تحتوي على 3 خصائص (Field,Operator,Value)

 rule.Field;
 rule.Operator;
 rule.Value;

هذه هي فئة القواعد الخاصة بي ...

الآن لدي حلقة

foreach(item in items)
   {
       // here I want to create a dynamic expression to evaluate at runtime
       // something like
       if (item.[rule.field] [rule.operator] [rule.value])
           { do work }
   }

أنا لا أعرف بناء الجملة، أو إذا كان ذلك ممكنًا في C#، فأنا أعلم أنه ممكن في JS ولكن هذه ليست لغة مجمعة.

تحديث

في الأساس أريد طريقة لذلك eval(stringCode) أو طريقة أفضل وأكثر دعمًا.

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

المحلول

لست متأكدًا تمامًا مما تقوله.هل يمكنك محاولة توضيح الأمر قليلاً؟

هل تريد أخذ تعبير سلسلة وتقييمه في وقت التشغيل في C#؟إذا كان الأمر كذلك، الجواب هو لا.لا يدعم C# مثل هذه الأنواع من التقييم الديناميكي.

نصائح أخرى

لا، C# لا يدعم أي شيء مثل هذا بشكل مباشر.

أقرب الخيارات هي:

  • قم بإنشاء برنامج C# كامل صالح وقم بتجميعه ديناميكيًا باستخدامه CSharpCodeProvider.
  • بناء شجرة التعبير, وتجميعها وتنفيذها
  • قم بإجراء التقييم بنفسك (قد يكون هذا أسهل في الواقع، اعتمادًا على المشغلين لديك وما إلى ذلك)

سيتعين عليك إما استخدام مكتبات CodeDOM أو إنشاء شجرة تعبير وتجميعها وتنفيذها.أعتقد أن بناء شجرة التعبير هو الخيار الأفضل.

بالطبع يمكنك وضع بيان التبديل على المشغل الخاص بك، وهذا ليس سيئًا لأن هناك عددًا محدودًا من المشغلين الذين يمكنك استخدامهم على أي حال.

إليك طريقة للقيام بذلك باستخدام أشجار التعبير (المكتوبة في LINQPad):

void Main()
{   
    var programmers = new List<Programmer>{ 
        new Programmer { Name = "Turing", Number = Math.E}, 
        new Programmer { Name = "Babbage", Number = Math.PI}, 
        new Programmer { Name = "Lovelace", Number = Math.E}};


    var rule0 = new Rule<string>() { Field = "Name", Operator = BinaryExpression.Equal, Value = "Turing" };
    var rule1 = new Rule<double>() { Field = "Number", Operator = BinaryExpression.GreaterThan,  Value = 2.719 };

    var matched0 = RunRule<Programmer, string>(programmers, rule0);
    matched0.Dump();

    var matched1 = RunRule<Programmer, double>(programmers, rule1);
    matched1.Dump();

    var matchedBoth = matched0.Intersect(matched1);
    matchedBoth.Dump();

    var matchedEither = matched0.Union(matched1);
    matchedEither.Dump();
}

public IEnumerable<T> RunRule<T, V>(IEnumerable<T> foos, Rule<V> rule) {

        var fieldParam = Expression.Parameter(typeof(T), "f");
        var fieldProp = Expression.Property (fieldParam, rule.Field);
        var valueParam = Expression.Parameter(typeof(V), "v");

        BinaryExpression binaryExpr = rule.Operator(fieldProp, valueParam);

        var lambda = Expression.Lambda<Func<T, V, bool>>(binaryExpr, fieldParam, valueParam);
        var func = lambda.Compile();

        foreach(var foo in foos) {
            var result = func(foo, rule.Value);
            if(result)
                yield return foo;
        }

}

public class Rule<T> {
    public string Field { get; set; }
    public Func<Expression, Expression, BinaryExpression> Operator { get; set; }
    public T Value { get; set; }
}

public class Programmer {
    public string Name { get; set; }
    public double Number { get; set; }
}

التصميم الأفضل بالنسبة لك هو أن تقوم قاعدتك بتطبيق الاختبار نفسه (أو على قيمة عشوائية)

من خلال القيام بذلك باستخدام مثيلات Func، ستحصل على أكبر قدر من المرونة، كما يلي:

IEnumerable<Func<T,bool> tests; // defined somehow at runtime
foreach (var item in items)
{
    foreach (var test in tests)
    {
       if (test(item))
       { 
           //do work with item 
       }
    }
}

عندها سيكون اختبارك المحدد شيئًا كهذا للتحقق من النوع القوي في وقت الترجمة:

public Func<T,bool> FooEqualsX<T,V>(V x)
{
    return t => EqualityComparer<V>.Default.Equals(t.Foo, x);
}

للحصول على شكل عاكس

public Func<T,bool> MakeTest<T,V>(string name, string op, V value)
{
    Func<T,V> getter;
    var f = typeof(T).GetField(name);
    if (f != null)      
    {
        if (!typeof(V).IsAssignableFrom(f.FieldType))
            throw new ArgumentException(name +" incompatible with "+ typeof(V));
        getter= x => (V)f.GetValue(x);
    }
    else 
    {
        var p = typeof(T).GetProperty(name);
        if (p == null)      
            throw new ArgumentException("No "+ name +" on "+ typeof(T));
        if (!typeof(V).IsAssignableFrom(p.PropertyType))
            throw new ArgumentException(name +" incompatible with "+ typeof(V));
        getter= x => (V)p.GetValue(x, null);
    }
    switch (op)
    {
        case "==":
            return t => EqualityComparer<V>.Default.Equals(getter(t), value);
        case "!=":
            return t => !EqualityComparer<V>.Default.Equals(getter(t), value);
        case ">":
            return t => Comparer<V>.Default.Compare(getter(t), value) > 0;
        // fill in the banks as you need to
        default:
            throw new ArgumentException("unrecognised operator '"+ op +"'");
    }
}   

إذا أردت أن تكون حقًا الاستبطان والتعامل مع أي حرفية دون معرفة أنه في وقت الترجمة يمكنك استخدام CSharpCodeProvider لتجميع وظيفة بافتراض شيء مثل:

 public static bool Check(T t)
 {
     // your code inserted here
 }

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

private Func<T,bool> Make<T>(string name, string op, string value)
{

    var foo = new Microsoft.CSharp.CSharpCodeProvider()
        .CompileAssemblyFromSource(
            new CompilerParameters(), 
            new[] { "public class Foo { public static bool Eval("+ 
                typeof(T).FullName +" t) { return t."+ 
                name +" "+ op +" "+ value 
                +"; } }" }).CompiledAssembly.GetType("Foo");
    return t => (bool)foo.InvokeMember("Eval",
        BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
        null, null, new object[] { t });
}

// use like so:
var f =  Make<string>("Length", ">", "2");

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

private bool Eval(object item, string name, string op, string value)
{

    var foo = new Microsoft.CSharp.CSharpCodeProvider()
        .CompileAssemblyFromSource(
            new CompilerParameters(), 
            new[] { "public class Foo { public static bool Eval("+ 
                item.GetType().FullName +" t) "+
               "{ return t."+ name +" "+ op +" "+ value +"; } }"   
            }).CompiledAssembly.GetType("Foo");
    return (bool)foo.InvokeMember("Eval",
        BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
        null, null, new object[] { item });
}

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

إذا كنت تريد أن تكون أكثر روعة، فيمكنك استخدام Reflection.Emit مع مثيلات DynamicMethod للقيام بذلك (باستخدام عوامل التشغيل المناسبة بدلاً من مثيلات المقارنة الافتراضية) ولكن هذا قد يتطلب معالجة معقدة للأنواع ذات عوامل التشغيل التي تم تجاوزها.

من خلال جعل رمز الشيك الخاص بك عامًا للغاية، يمكنك تضمين المزيد من الاختبارات في المستقبل حسب حاجتك لذلك.قم بشكل أساسي بعزل جزء التعليمات البرمجية الخاص بك الذي يهتم فقط بوظيفة ما من t -> true/false من التعليمات البرمجية التي توفر هذه الوظائف.

تنصل:أنا صاحب المشروع تقييم التعبير.NET

هذه المكتبة قريبة من كونها مكافئة لـ JS Eval.يمكنك تقريبًا تقييم وتجميع كل لغة C#.

فيما يلي مثال بسيط لاستخدام سؤالك، لكن المكتبة تتجاوز هذا السيناريو البسيط.

int field = 2;
int value = 1;
string binaryOperator = ">";

string formula = "x " + binaryOperator + " y";

// For single evaluation
var value1 = Eval.Execute<bool>(formula, new { x = field, y = value });

// For many evaluation
var compiled = Eval.Compile<Func<int, int, bool>>(formula, "x", "y");
var value2 = compiled(field, value);

CSharpCodeProvider; switch العبارات التي تختار "العوامل" المختلفة المناسبة؛دي إل آر ...إنها جميعها طرق يمكنك من خلالها القيام بذلك؛لكنها تبدو حلولاً غريبة بالنسبة لي.

ماذا عن مجرد استخدام المندوبين؟

على افتراض الخاص بك Field و Value هي أرقام، أعلن شيئا مثل هذا:

delegate bool MyOperationDelegate(decimal left, decimal right);
...
class Rule {
    decimal Field;
    decimal Value;
    MyOperationDelegate Operator;
}

يمكنك الآن تحديد "قاعدتك" على أنها، على سبيل المثال، مجموعة من لامدا:

Rule rule1 = new Rule;
rule1.Operation = (decimal l, decimal r) => { return l > r; };
rule1.Field = ... 

يمكنك إنشاء صفائف من القواعد وتطبيقها بالطريقة التي تريدها.

IEnumerable<Rule> items = ...;

foreach(item in items)
{
    if (item.Operator(item.Field, item.Value))
    { /* do work */ }
}

لو Field و Values ليست أرقامًا، أو أن النوع يعتمد على القاعدة المحددة التي يمكنك استخدامها object بدلاً من decimal, ، ومع قليل من الصب يمكنك جعل كل شيء يعمل.

هذا ليس التصميم النهائي.إنها فقط لإعطائك بعض الأفكار (على سبيل المثال، من المحتمل أن تطلب من الفصل تقييم المندوب بمفرده عبر ملف Check() طريقة أو شيء من هذا).

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

في حين أنه من الصحيح أنك ربما لن تجد طريقة أنيقة لتقييم كود C# الكامل بسرعة دون استخدام تجميع التعليمات البرمجية ديناميكيًا (وهو أمر غير جميل أبدًا)، فمن المؤكد تقريبًا أنه يمكنك تقييم قواعدك في وقت قصير باستخدام إما DLR (IronPython، IronRuby، إلخ) أو مكتبة مقيم التعبير التي تقوم بتوزيع وتنفيذ بناء جملة مخصص.هناك واحد، Script.NET، يوفر بناء جملة مشابه جدًا لـ C#.

ألق نظرة هنا:تقييم التعبيرات في وقت التشغيل في .NET(C#)

إذا كان لديك الوقت/الميل لتعلم القليل من لغة Python، فسوف يقوم IronPython وDLR بحل جميع مشكلاتك:توسيع تطبيقك باستخدام IronPython

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