Como posso avaliar o código C # dinamicamente?


92

Posso fazer um eval("something()");para executar o código dinamicamente em JavaScript. Existe uma maneira de fazer a mesma coisa em C #?

Um exemplo do que estou tentando fazer é: tenho uma variável inteira (digamos i) e várias propriedades pelos nomes: "Propriedade1", "Propriedade2", "Propriedade3" etc. Agora, quero realizar algumas operações na propriedade "Propriedade i " dependendo do valor de i.

Isso é muito simples com Javascript. Existe alguma maneira de fazer isso com C #?



2
c # chamar a avaliação de ironpython. Eu tentei em c # 4.0. nenhuma experiência com c # 2.0
Peter Long

@Peter Long, onde posso encontrar documentação sobre a avaliação do IronPython?
smartcaveman

@AdhipGupta Eu sei que este Q&A é bem datado, mas acabei de lançar uma lista de reprodução de vídeo que se parece muito com a descrição dada na resposta de Davide Icardi. É dramaticamente diferente e provavelmente vale a pena conferir.
Rick Riggs

Respostas:


49

Infelizmente, C # não é uma linguagem dinâmica como essa.

O que você pode fazer, no entanto, é criar um arquivo de código-fonte C #, completo com classe e tudo, e executá-lo por meio do provedor CodeDom para C # e compilá-lo em um assembly e, em seguida, executá-lo.

Esta postagem do fórum no MSDN contém uma resposta com alguns exemplos de código na página:
criar um método anônimo a partir de uma string?

Eu dificilmente diria que esta é uma solução muito boa, mas é possível de qualquer maneira.

Que tipo de código você espera nessa string? Se for um subconjunto menor de código válido, por exemplo apenas expressões matemáticas, pode ser que existam outras alternativas.


Edit : Bem, isso me ensina a ler as perguntas cuidadosamente primeiro. Sim, a reflexão poderia ajudá-lo aqui.

Se você dividir a string pelo; primeiro, para obter propriedades individuais, você pode usar o código a seguir para obter um objeto PropertyInfo para uma propriedade específica de uma classe e, em seguida, usar esse objeto para manipular um objeto específico.

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

Link: Método PropertyInfo.SetValue


e se GetProperty tiver que ser uma função com parâmetros x, y?, significa Text (1,2)?
user1735921

@ user1735921 Então você precisará usar em GetMethod(methodName)vez disso, analisar os valores dos parâmetros e chamar o método usando reflexão.
Lasse V. Karlsen

typeof (ObjectType) é análogo a someObject.GetType ()
Thiago

32

Usando a API de script Roslyn (mais exemplos aqui ):

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

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

Você também pode executar qualquer código:

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

E referencie o código que foi gerado em execuções anteriores:

await script.ContinueWithAsync("new MyClass().Print();");

14

Na verdade não. Você pode usar a reflexão para alcançar o que deseja, mas não será tão simples como em Javascript. Por exemplo, se você quiser definir o campo privado de um objeto como algo, pode usar esta função:

protected static void SetField(object o, string fieldName, object value)
{
   FieldInfo field = o.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
   field.SetValue(o, value);
}

11

Esta é uma função eval em c #. Usei-o para converter funções anônimas (expressões Lambda) de uma string. Fonte: 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;

}

1
@sehe Opa, eu corrigi o erro de digitação (Lambada => Lambda). Eu não sabia que a música se chamava Lambada, então essa não foi intencional. ;)
Largo

Eu não conseguia entender por que essa resposta recebe menos votos. É muito útil.
Muzaffer Galata

9

Eu escrevi um projeto de código aberto, Dynamic Expresso , que pode converter a expressão de texto escrita usando uma sintaxe C # em delegados (ou árvore de expressão). As expressões são analisadas e transformadas em Árvores de Expressão sem usar compilação ou reflexão.

Você pode escrever algo como:

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

ou

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);

Meu trabalho é baseado no artigo de Scott Gu http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx .


7

Tudo isso definitivamente funcionaria. Pessoalmente, para esse problema específico, provavelmente faria uma abordagem um pouco diferente. Talvez algo assim:

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
  }
}

Ao usar padrões como esse, você deve ter cuidado para que seus dados sejam armazenados por referência e não por valor. Em outras palavras, não faça isso com primitivos. Você tem que usar suas contrapartes de classe grandes e inchadas.

Percebi que essa não é exatamente a pergunta, mas ela foi muito bem respondida e pensei que talvez uma abordagem alternativa pudesse ajudar.


5

Não sei se você deseja mesmo executar instruções C #, mas já pode executar instruções Javascript em C # 2.0. A biblioteca de código aberto Jint é capaz de fazer isso. É um interpretador Javascript para .NET. Passe um programa Javascript e ele rodará dentro da sua aplicação. Você pode até mesmo passar objetos C # como argumentos e fazer automação nele.

Além disso, se você quiser apenas avaliar a expressão em suas propriedades, experimente NCalc .


3

Você pode usar reflexão para obter a propriedade e invocá-la. Algo assim:

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

Ou seja, supondo que o objeto que possui a propriedade seja denominado "theObject" :)


2

Você também pode implementar um navegador da Web e, em seguida, carregar um arquivo html que contém javascript.

Então você vai para o document.InvokeScriptMétodo neste navegador. O valor de retorno da função eval pode ser capturado e convertido em tudo o que você precisa.

Fiz isso em vários Projetos e funciona perfeitamente.

Espero que ajude


0

Você poderia fazer isso com uma função de protótipo:

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);
}

e assim por diante...



0

Eu escrevi um pacote, SharpByte.Dynamic , para simplificar a tarefa de compilar e executar código dinamicamente. O código pode ser chamado em qualquer objeto de contexto usando métodos de extensão, conforme detalhado mais aqui .

Por exemplo,

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

retorna 3;

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

retorna a representação de string do objeto de contexto;

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

executa essas instruções como um script, etc.

Os executáveis ​​podem ser obtidos facilmente usando um método de fábrica, como visto no exemplo aqui - tudo que você precisa é o código-fonte e a lista de quaisquer parâmetros nomeados esperados (os tokens são incorporados usando a notação de colchetes triplos, como {{{0}} }, para evitar colisões com string.Format (), bem como sintaxes semelhantes a Handlebars):

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

Cada objeto executável (script ou expressão) é thread-safe, pode ser armazenado e reutilizado, suporta registro de dentro de um script, armazena informações de tempo e última exceção se encontrada, etc. Há também um método Copy () compilado em cada um para permitir criar cópias baratas, ou seja, usar um objeto executável compilado de um script ou expressão como modelo para criar outros.

A sobrecarga de execução de um script ou instrução já compilado é relativamente baixa, menos de um microssegundo em um hardware modesto, e os scripts e expressões já compilados são armazenados em cache para reutilização.


0

Eu estava tentando obter o valor de um membro da estrutura (classe) pelo seu nome. A estrutura não era dinâmica. Todas as respostas não funcionaram até que eu finalmente consegui:

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

Este método retornará o valor do membro por seu nome. Funciona em estrutura regular (classe).


0

Você pode verificar a biblioteca Heleonix.Reflection . Ele fornece métodos para obter / definir / invocar membros dinamicamente, incluindo membros aninhados, ou se um membro estiver claramente definido, você pode criar um getter / setter (lambda compilado em um delegado) que é mais rápido que a reflexão:

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

Ou se o número de propriedades não for infinito, você pode gerar setters e chache-los (setters são mais rápidos, pois são delegados compilados):

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

Os setters podem ser do tipo, Action<object, object>mas as instâncias podem ser diferentes em tempo de execução, portanto, você pode criar listas de setters.


-2

Infelizmente, C # não possui nenhum recurso nativo para fazer exatamente o que você está pedindo.

No entanto, meu programa de avaliação em C # permite avaliar o código C #. Ele fornece avaliação de código C # em tempo de execução e oferece suporte a muitas instruções C #. Na verdade, esse código pode ser usado em qualquer projeto .NET; no entanto, ele está limitado ao uso da sintaxe C #. Dê uma olhada em meu site, http://csharp-eval.com , para obter detalhes adicionais.


Dê uma olhada em Roslyn Scripting API
AlexMelw

-9

a resposta correta é que você precisa armazenar em cache todos os resultados para manter baixo o uso de memória.

um exemplo seria parecido com este

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

e adicioná-lo a uma lista

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

salve o id e use-o no código

espero que isto ajude


5
alguém confundiu avaliação com pesquisa. Se você conhece todos os programas possíveis ( acho que é pelo menos NP-Hard) ... e você tem uma supermáquina para pré-compilar todos os resultados possíveis ... e não há efeitos colaterais / entradas externas ... Sim, essa ideia teoricamente funciona . O código é um grande erro de sintaxe, no entanto
veja
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.