Jak mogę dynamicznie oceniać kod C #?

93

Mogę zrobić eval("something()"); wykonać kod dynamicznie w JavaScript. Czy istnieje sposób, aby zrobić to samo w C #?

Przykładem tego, co próbuję zrobić, jest: Mam zmienną całkowitą (powiedzmy i) i mam wiele właściwości o nazwach: „Właściwość1”, „Właściwość2”, „Właściwość3” itd. Teraz chcę wykonać kilka operacji na właściwości „Property i ” w zależności od wartości i.

Z Javascriptem jest to naprawdę proste. Czy jest jakiś sposób, aby to zrobić za pomocą C #?

Adhip Gupta
źródło
2
c # zadzwoń do eval ironpythona. Wypróbowałem to w C # 4.0. brak doświadczenia z c # 2.0
Peter Long
@Peter Long, gdzie mogę znaleźć dokumentację dotyczącą eval IronPython?
smartcaveman
@AdhipGupta Wiem, że te pytania i odpowiedzi są dość przestarzałe, ale właśnie opublikowałem listę odtwarzania wideo, która brzmi bardzo podobnie do opisu podanego w odpowiedzi Davide Icardi. Jest dramatycznie inny i prawdopodobnie warto to sprawdzić.
Rick Riggs

Odpowiedzi:

49

Niestety, C # nie jest takim dynamicznym językiem.

Możesz jednak utworzyć plik kodu źródłowego w języku C #, pełen klas i wszystkim, i uruchomić go za pośrednictwem dostawcy CodeDom dla C # i skompilować go w zestawie, a następnie wykonać.

Ten post na forum w witrynie MSDN zawiera odpowiedź z przykładowym kodem nieco poniżej strony:
utworzyć anonimową metodę na podstawie ciągu znaków?

Trudno powiedzieć, że jest to bardzo dobre rozwiązanie, ale i tak jest możliwe.

Jakiego rodzaju kodu będziesz się spodziewać w tym ciągu? Jeśli jest to niewielki podzbiór prawidłowego kodu, na przykład tylko wyrażenia matematyczne, może to oznaczać, że istnieją inne alternatywy.


Edycja : Cóż, to uczy mnie najpierw dokładnie przeczytać pytania. Tak, refleksja może ci tu pomóc.

Jeśli podzielisz ciąg przez; po pierwsze, aby uzyskać indywidualne właściwości, możesz użyć następującego kodu w celu pobrania obiektu PropertyInfo dla określonej właściwości dla klasy, a następnie użyć tego obiektu do manipulowania określonym obiektem.

String propName = "Text";
PropertyInfo pi = someObject.GetType().GetProperty(propName);
pi.SetValue(someObject, "New Value", new Object[0]);

Link: Metoda PropertyInfo.SetValue

Lasse V. Karlsen
źródło
co, jeśli GetProperty ma być funkcją z parametrami x, y?, czyli Text (1,2)?
user1735921
@ user1735921 Następnie będziesz musiał użyć GetMethod(methodName)zamiast tego przeanalizować wartości parametrów i wywołać metodę przy użyciu odbicia.
Lasse V. Karlsen
typeof (ObjectType) jest analogiczne do someObject.GetType ()
Thiago
35

Korzystanie z interfejsu API skryptów Roslyn (więcej przykładów tutaj ):

// add NuGet package 'Microsoft.CodeAnalysis.Scripting'
using Microsoft.CodeAnalysis.CSharp.Scripting;

await CSharpScript.EvaluateAsync("System.Math.Pow(2, 4)") // returns 16

Możesz także uruchomić dowolny fragment kodu:

var script = await CSharpScript.RunAsync(@"
                class MyClass
                { 
                    public void Print() => System.Console.WriteLine(1);
                }")

I odwołaj się do kodu, który został wygenerowany w poprzednich uruchomieniach:

await script.ContinueWithAsync("new MyClass().Print();");
Eli Arbel
źródło
14

Nie całkiem. Możesz użyć refleksji, aby osiągnąć to, co chcesz, ale nie będzie to tak proste, jak w Javascript. Na przykład, jeśli chcesz ustawić na coś pole prywatne obiektu, możesz użyć tej funkcji:

protected static void SetField(object o, string fieldName, object value)
{
   FieldInfo field = o.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
   field.SetValue(o, value);
}
Karl Seguin
źródło
11

To jest funkcja eval w języku C #. Użyłem go do konwersji funkcji anonimowych (wyrażeń lambda) z łańcucha. Źródło: http://www.codeproject.com/KB/cs/evalcscode.aspx

public static object Eval(string sCSCode) {

  CSharpCodeProvider c = new CSharpCodeProvider();
  ICodeCompiler icc = c.CreateCompiler();
  CompilerParameters cp = new CompilerParameters();

  cp.ReferencedAssemblies.Add("system.dll");
  cp.ReferencedAssemblies.Add("system.xml.dll");
  cp.ReferencedAssemblies.Add("system.data.dll");
  cp.ReferencedAssemblies.Add("system.windows.forms.dll");
  cp.ReferencedAssemblies.Add("system.drawing.dll");

  cp.CompilerOptions = "/t:library";
  cp.GenerateInMemory = true;

  StringBuilder sb = new StringBuilder("");
  sb.Append("using System;\n" );
  sb.Append("using System.Xml;\n");
  sb.Append("using System.Data;\n");
  sb.Append("using System.Data.SqlClient;\n");
  sb.Append("using System.Windows.Forms;\n");
  sb.Append("using System.Drawing;\n");

  sb.Append("namespace CSCodeEvaler{ \n");
  sb.Append("public class CSCodeEvaler{ \n");
  sb.Append("public object EvalCode(){\n");
  sb.Append("return "+sCSCode+"; \n");
  sb.Append("} \n");
  sb.Append("} \n");
  sb.Append("}\n");

  CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
  if( cr.Errors.Count > 0 ){
      MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText, 
         "Error evaluating cs code", MessageBoxButtons.OK, 
         MessageBoxIcon.Error );
      return null;
  }

  System.Reflection.Assembly a = cr.CompiledAssembly;
  object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");

  Type t = o.GetType();
  MethodInfo mi = t.GetMethod("EvalCode");

  object s = mi.Invoke(o, null);
  return s;

}
Largo
źródło
1
@sehe Ups, poprawiłem literówkę (Lambada => Lambda). Nie wiedziałem, że piosenka nazywa się Lambada, więc ta była niezamierzona. ;)
Largo,
Nie mogłem zrozumieć, dlaczego ta odpowiedź ma mniej głosów. To jest bardzo użyteczne.
Muzaffer Galata
9

Napisałem projekt typu open source, Dynamic Expresso , który może konwertować wyrażenia tekstowe napisane przy użyciu składni C # na delegatów (lub drzewo wyrażeń). Wyrażenia są analizowane i przekształcane w drzewa wyrażeń bez użycia kompilacji lub odbicia.

Możesz napisać coś takiego:

var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");

lub

var interpreter = new Interpreter()
                      .SetVariable("service", new ServiceExample());

string expression = "x > 4 ? service.SomeMethod() : service.AnotherMethod()";

Lambda parsedExpression = interpreter.Parse(expression, 
                          new Parameter("x", typeof(int)));

parsedExpression.Invoke(5);

Moja praca opiera się na artykule Scotta Gu http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx .

Davide Icardi
źródło
7

Wszystko to na pewno by się udało. Osobiście w przypadku tego konkretnego problemu prawdopodobnie podjąłbym nieco inne podejście. Może coś takiego:

class MyClass {
  public Point point1, point2, point3;

  private Point[] points;

  public MyClass() {
    //...
    this.points = new Point[] {point1, point2, point3};
  }

  public void DoSomethingWith(int i) {
    Point target = this.points[i+1];
    // do stuff to target
  }
}

Korzystając z takich wzorców, musisz uważać, aby Twoje dane były przechowywane przez odniesienie, a nie według wartości. Innymi słowy, nie rób tego z prymitywami. Musisz użyć ich wielkich, nadętych klasowych odpowiedników.

Zdałem sobie sprawę, że to nie jest dokładnie to pytanie, ale odpowiedź na nie jest całkiem dobra i pomyślałem, że może pomoże alternatywne podejście.

MojoFilter
źródło
5

Nie robię tego teraz, jeśli absolutnie chcesz wykonywać instrukcje C #, ale możesz już wykonywać instrukcje Javascript w C # 2.0. Biblioteka open source Jint jest w stanie to zrobić. Jest to interpreter JavaScript dla .NET. Przekaż program Javascript, a będzie on działał w Twojej aplikacji. Możesz nawet przekazać obiekt C # jako argumenty i wykonać na nim automatyzację.

Jeśli chcesz po prostu oszacować wyrażenie na swoich właściwościach, wypróbuj NCalc .

Sébastien Ros - MSFT
źródło
3

Możesz użyć odbicia, aby pobrać właściwość i wywołać ją. Coś takiego:

object result = theObject.GetType().GetProperty("Property" + i).GetValue(theObject, null);

Oznacza to, że zakładając, że obiekt, który ma tę właściwość, nosi nazwę „theObject” :)

David Wengier
źródło
2

Możesz także zaimplementować przeglądarkę internetową, a następnie załadować plik html, który zawiera JavaScript.

Następnie przejdź do document.InvokeScriptmetody w tej przeglądarce. Wartość zwracaną przez funkcję eval można przechwycić i przekonwertować na wszystko, czego potrzebujesz.

Zrobiłem to w kilku projektach i działa idealnie.

Mam nadzieję, że to pomoże

Xodem
źródło
0

Możesz to zrobić za pomocą funkcji prototypu:

void something(int i, string P1) {
    something(i, P1, String.Empty);
}

void something(int i, string P1, string P2) {
    something(i, P1, P2, String.Empty);
}

void something(int i, string P1, string P2, string P3) {
    something(i, P1, P2, P3, String.Empty);
}

i tak dalej...

GateKiller
źródło
0

Używa odbicia do analizowania i oceniania wyrażenia powiązania danych względem obiektu w czasie wykonywania.

Metoda DataBinder.Eval

Jan Bannister
źródło
0

Napisałem pakiet SharpByte.Dynamic , aby uprościć zadanie kompilowania i dynamicznego wykonywania kodu. Kod można wywołać na dowolnym obiekcie kontekstu przy użyciu metod rozszerzających, jak opisano szczegółowo tutaj .

Na przykład,

someObject.Evaluate<int>("6 / {{{0}}}", 3))

zwraca 3;

someObject.Evaluate("this.ToString()"))

zwraca reprezentację ciągu obiektu kontekstu;

someObject.Execute(@
"Console.WriteLine(""Hello, world!"");
Console.WriteLine(""This demonstrates running a simple script"");
");

uruchamia te instrukcje jako skrypt itp.

Pliki wykonywalne można łatwo uzyskać za pomocą metody fabrycznej, jak pokazano na poniższym przykładzie - wszystko , czego potrzebujesz, to kod źródłowy i lista wszelkich oczekiwanych nazwanych parametrów (tokeny są osadzane przy użyciu notacji z potrójnym nawiasem, np. {{{0}} }, aby uniknąć kolizji z string.Format (), a także składniami podobnymi do Handlebars):

IExecutable executable = ExecutableFactory.Default.GetExecutable(executableType, sourceCode, parameterNames, addedNamespaces);

Każdy obiekt wykonywalny (skrypt lub wyrażenie) jest bezpieczny dla wątków, może być przechowywany i ponownie używany, obsługuje logowanie z poziomu skryptu, przechowuje informacje o czasie i ostatnim wyjątku, jeśli zostanie napotkany itp. Istnieje również skompilowana metoda Copy (), która umożliwia tworzenie tanich kopii, czyli używanie obiektu wykonywalnego skompilowanego ze skryptu lub wyrażenia jako szablonu do tworzenia innych.

Narzut związany z wykonaniem już skompilowanego skryptu lub instrukcji jest stosunkowo niski, znacznie poniżej mikrosekundy na skromnym sprzęcie, a już skompilowane skrypty i wyrażenia są buforowane do ponownego wykorzystania.

Iucounu
źródło
0

Próbowałem uzyskać wartość elementu struktury (klasy) według jego nazwy. Struktura nie była dynamiczna. Wszystkie odpowiedzi nie działały, dopóki w końcu ich nie otrzymałem:

public static object GetPropertyValue(object instance, string memberName)
{
    return instance.GetType().GetField(memberName).GetValue(instance);
}

Ta metoda zwróci wartość elementu członkowskiego według jego nazwy. Działa na regularnej strukturze (klasie).

Ilya Dan
źródło
0

Możesz sprawdzić bibliotekę Heleonix.Reflection . Zapewnia metody dynamicznego pobierania / ustawiania / wywoływania elementów członkowskich, w tym zagnieżdżonych elementów członkowskich, lub jeśli element członkowski jest jasno zdefiniowany, można utworzyć metodę pobierającą / ustawiającą (lambda skompilowaną do delegata), która jest szybsza niż odbicie:

var success = Reflector.Set(instance, null, $"Property{i}", value);

Lub jeśli liczba właściwości nie jest nieskończona, możesz wygenerować setery i zmienić je (setery są szybsze, ponieważ są skompilowanymi delegatami):

var setter = Reflector.CreateSetter<object, object>($"Property{i}", typeof(type which contains "Property"+i));
setter(instance, value);

Setery mogą być typu, Action<object, object>ale instancje mogą być różne w czasie wykonywania, więc możesz tworzyć listy seterów.

Hennadii Lutsyshyn
źródło
-1

Niestety, C # nie ma żadnych natywnych możliwości robienia dokładnie tego, o co prosisz.

Jednak mój program C # eval pozwala na ocenę kodu C #. Zapewnia ocenę kodu C # w czasie wykonywania i obsługuje wiele instrukcji C #. W rzeczywistości ten kod jest używany w każdym projekcie .NET, jednak jest on ograniczony do używania składni C #. Zajrzyj na moją stronę internetową http://csharp-eval.com , aby uzyskać dodatkowe informacje.

xnaterm
źródło
Spójrz na Roslyn Scripting API
AlexMelw
-9

Prawidłowa odpowiedź brzmi: musisz buforować wszystkie wyniki, aby utrzymać niskie zużycie pamięci.

przykład wyglądałby tak

TypeOf(Evaluate)
{
"1+1":2;
"1+2":3;
"1+3":5;
....
"2-5":-3;
"0+0":1
} 

i dodaj go do listy

List<string> results = new List<string>();
for() results.Add(result);

zapisz identyfikator i użyj go w kodzie

mam nadzieję że to pomoże

strych
źródło
5
ktoś pomylił ocenę z wyszukiwaniem. Jeśli znasz wszystkie możliwe programy ( myślę, że to przynajmniej NP-Hard) ... i masz supermaszynę do prekompilacji wszystkich możliwych wyników ... i nie ma żadnych skutków ubocznych / zewnętrznych danych wejściowych ... Tak, ten pomysł teoretycznie działa . Kod jest jednak jednym dużym błędem składniowym
patrz