
Feliz ano novo, pessoal! Vamos começar o ano acelerando.
Volto a falar de IronRuby com C#. Este é o segundo post sobre o assunto, e não vou voltar no básico. Para isso leia o primeiro post, onde mostro como configurar o básico dos testes, baixar e configurar o IronRuby, e preparar o resto do ambiente:
- Rodando Ruby com C#: Parte 1
Nesse post vou mostrar como criar uma classe no IronRuby e chamar ela no C# 4 com Visual Studio 2010.
A classe Ruby que quero rodar contém apenas um método, e não faz nada de mais:
class Teste2
def Chamar(flag, texto)
if !flag
texto
else
texto + 'sufixo'
end
end
end
A idéia é criar uma instância dessa classe e após declará-la, retorná-la, tudo no mesmo script. No Ruby, a última linha retorna o resultado. Pra isso, então basta incluir:
Teste2.new
Ok, para fazer isso funcionar, é igualzinho o que é feito no post anterior, que fazia uma soma simples:
[TestMethod]
public void ConsigoCriarUmObjetoRubyEChamarUmMetodoDinamicamente()
{
var source = @"
class Teste2
def Chamar(flag, texto)
if !flag
texto
else
texto + 'sufixo'
end
end
end
Teste2.new";
var scriptSource = _defaultScriptEngine.CreateScriptSourceFromString(source);
var teste = scriptSource.Execute();
//continua...
A linha 17 está iluminada porque ela é quem executa o código. Adivinham qual o tipo da variável “teste”? Um Ruby de pelúcia pra quem disse “dynamic” (mais sobre ela aqui). No exemplo anterior eu fazia cast para inteiro, porque o resultado era um inteiro, produto da soma de 1 mais 2. E agora o resultado é uma classe que só existe no Ruby, e não existe em lugar nenhum no C#, e portanto não pode ser chamada estaticamente. E o C# é uma linguagem estaticamente tipada, ele não conhece a classe Teste2 declarada no Ruby. Só que na versão 4 ele consegue realizar Dynamic Dispatch, enviando a chamada ao método dinamicamente (mais informação sobre Dynamic Dispatch em uma discussão que tivemos no .Net Architects).
Isso significa que consigo chamar quaisquer métodos e propriedades que existam no objeto Ruby. E é isso que faço:
//continuando...
var resultado = teste.Chamar(true, "ttt");
Assert.AreEqual("tttsufixo", resultado);
}
Na linha 2 estou executando o método “Chamar”, via dynamic dispatch, que existe na classe Teste2, declarada no IronRuby, e executando seu código. Na linha 3 eu verifico se o resultado era o esperado. O código todo do teste fica assim (apenas repetindo para deixar mais claro):
[TestMethod]
public void ConsigoCriarUmObjetoRubyEChamarUmMetodoDinamicamente()
{
var source = @"
class Teste2
def Chamar(flag, texto)
if !flag
texto
else
texto + 'sufixo'
end
end
end
Teste2.new";
var scriptSource = _defaultScriptEngine.CreateScriptSourceFromString(source);
var teste = scriptSource.Execute();
var resultado = teste.Chamar(true, "ttt");
Assert.AreEqual("tttsufixo", resultado);
}
E se eu não quiser escrever o corpo do método Ruby no C#, mas defini-lo em outro arquivo? Não tem problema, o ScriptSource sabe executar arquivos também, além de texto puro. Veja este outro teste, onde faço exatamente isso:
[TestMethod]
public void ConsigoCriarUmObjetoRubyAPartirDeUmArquivoEChamarUmMetodoDinamicamente()
{
var scriptSource = _defaultScriptEngine.CreateScriptSourceFromFile(
ObterCaminhoArquivo("RubyTeste.rb"),
Encoding.UTF8);
var teste = scriptSource.Execute();
var resultado = teste.Chamar(true, "ttt");
Assert.AreEqual("tttsufixo", resultado);
}
O método CreateScriptSourceFromFile lê o arquivo e executa. O arquivo RubyTeste.rb contém praticamente o mesmo código utilizado no teste anterior. Coloquei ele na estrutura do projeto:
Marquei nas propriedades do arquivo o parâmetro “Copy to Output Directory” como “Copy if newer”, o que garante que o arquivo vai estar no mesmo diretório da dll de testes. O método ObterCaminhoArquivo sabe que deve procurar os arquivos Ruby no diretório especificado, olhando na pasta onde a dll está:
private static string ObterCaminhoArquivo(string arquivo)
{
return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), arquivo);
}
(Fique atento para colocar o encoding correto no arquivo, no meu caso, UTF8, ou vai dar erro na hora de rodar.)
Para executar o script de definição da classe, mas só criar ela quando quisermos é fácil. Aqui está um teste que faz exatamente isso:
[TestMethod]
public void ConsigoExecutarUmArquivoRubyECriarOObjetoDinamicamente()
{
var scriptCriaClasse = _defaultScriptEngine.CreateScriptSourceFromFile(
ObterCaminhoArquivo("RubyTeste2.rb"),
Encoding.UTF8);
scriptCriaClasse.Execute();
var scriptInstanciaObjeto = _defaultScriptEngine.CreateScriptSourceFromString("Teste4.new");
var teste = scriptInstanciaObjeto.Execute();
var resultado = teste.Chamar(true, "ttt");
Assert.AreEqual("tttsufixo", resultado);
}
O arquivo RubyTeste2 tem a definição da classe, mas não cria e retorna, ou seja, o retorno é nulo:
class Teste4
def Chamar(flag, texto)
if !flag
texto
else
texto + 'sufixo'
end
end
end
Na linha 7 do teste eu executo este arquivo, e na linha 9 eu executo a criação da classe. Como o código está executando sob o mesmo contexto, o mesmo ScriptEngine, a definição da classe ainda está no escopo, ele cria uma instância da classe na linha 9 e retorna.
Pelo que andei pesquisando, há outras maneiras de fazer isso, mas só como o método Execute do ScriptSource, e os métodos CreateScriptSourceFromString e CreateScriptSourceFromFile do ScriptEngine você já faz quase toda a interação básica.
Interessante, certo? Já podemos definir arquivos Ruby e executá-los dinamicamente. Será que o IronRuby consegue ele mesmo criar e interagir com objetos .Net? Não perca as cenas do próximo capítulo.