IronRuby

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:

  1. 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:

image

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.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.