Welcome to GASP Sign in | Join | Help

Paulo Morgado

Tudo sobre Arquitectura de Software

Localização dos Visitantes

  • Localização dos Visitantes

Livros

  • LINQ com C#

Eventos

Renûncia

As opiniões e pontos de vista expressos neste sítio são minhas e podem não reflectir as da Microsoft, do meu empregador, ou de qualquer comunidade a que pertença. Qualquer código ou opinião é oferecido sem qualquer garantia. Os produtos ou serviços mencionados são comprados por mim, disponibilizados pelo meu empregador ou pelo fabricante/vendedor o que não influencia em nada a minha opinião.

Hidratando Objectos Usando Árvores De Expressões - Parte III
LINQ Com C#

Para finalizar esta série acerca da hidratação de objectos, vou mostrar algumas comparações de performance entre os diferentes métodos de hidratação de objectos.

Para os efeitos deste exercício vou usar esta classe:

class SomeType
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTimeOffset CreationTime { get; set; }
    public Guid UniqueId { get; set; }
}

e este conjunto de dados:

var data = (
    from i in Enumerable.Range(1, ObjectCount)
    select new object[] { i, i.ToString(), DateTimeOffset.Now, Guid.NewGuid() }
).ToArray();

Os dados abaixo mostram o tempo (em segundos) para diferentes execuções com diferentes valores de ObjectCount (na mesma máquina e com aproximadamente a mesma carga) assim como a sua disponibilidade nas diferentes versões da Plataforma .NET e da linguagem de programação C#:

10000 100000 1000000 Válido para
Inicialização Hydrate Total Inicialização Hydrate Total Inicialização Hydrate Total Versão de .NET Versão de C#
Activation e afectação por Reflection 0.060 0.101 0.161 0.055 0.736 0.791 0.054 6.822 6.876 1.0, 1.1, 2.0, 3.5, 4.0 1.0, 2.0, 3.0, 4.0
Activation e afectação por árvore de expressões 0.300 0.003 0.303 0.313 0.049 0.359 0.293 0.578 0.871 4.0 none
Inicializador de membros 0.035 0.001 0.036 0.039 0.027 0.066 0.041 0.518 0.559 3.5, 4.0 3.0, 4.0

Estes valores variam de acordo com o número de objectos a hidratar e o seu número de propriedades, mas o método usando Inicializador de membros será o mais performante.

Exemplos de código para esta série de entradas (e para a entrada acerca de como despejar objectos usando árvores de expressões) pode ser encontrados na minha MSDN Code Gallery: Dump And Hydrate Objects With Expression Trees

Hidratando Objectos Usando Árvores De Expressões - Parte II

LINQ Com C#

Na minha entrada mostrei como hidratar objectos criando instâncias e afectando propriedades dessas instâncias.

Mas, se a intenção é hidratar objectos  partir de dados, porque não ter uma expressão que faz isso mesmo? É para isso memos que serve a expressão de inicialização de membro.

Para criar tal expressão apenas é necessário uma expressão para o construtor e expressões para ligar as propriedades aos dados:

var properties = objectType.GetProperties();
var bindings = new MemberBinding[properties.Length];
var valuesArrayExpression = Expression.Parameter(typeof(object[]), "v");

for (int p = 0; p < properties.Length; p++)
{
    var property = properties[p];

    bindings[p] = Expression.Bind(
        property,
        Expression.Convert(
            Expression.ArrayAccess(
                valuesArrayExpression,
                Expression.Constant(p, typeof(int))
            ),
            property.PropertyType
        )
    );
}

var memberInitExpression = Expression.MemberInit(
    Expression.New(objectType),
    bindings
);

var objectHidrationExpression = Expression.Lambda<Func<object[], object>>(memberInitExpression, valuesArrayExpression);

var compiledObjectHidrationExpression = objectHidrationExpression.Compile();

Pode parecer mais complexo que a solução anterior, mas é tão simples de usar como isto:

for (int o = 0; o < objects.Length; o++)
{
    newObjects[o] = compiledObjectHidrationExpression(objects[o]);
}
Hidratando Objectos Usando Árvores De Expressões - Parte I

LINQ Com C#

Após a minha entrada acerca de como despejar objectos usando árvores de expressões, têm-me perguntado se o mesmo pode ser feito para hidratar objectos.

Claro que pode, mas poderá não ser tão fácil.

O que procuramos é uma forma de afectar propriedades em objectos de que desconhecemos o tipo. Para tal, necessitamos de gerar métodos que afectem o valor de cada propriedade dos objectos em questão.

Tais métodos assemelhariam-se a esta expressão:

Expression<Action<object, object>> expression = (o, v) => ((SomeType)o).Property1 = (PropertyType)v;

Infelizmente, não podemos usar o truque do .NET Reflector porque, se se tentar compilar isto, obtemos o seguinte erro:

error CS0832: An expression tree may not contain an assignment operator

Felizmente, a expressão corresponde a uma árvore de expressões valida em .NET. Apenas temos de a construir manualmente.

Assim sendo, para um determinado tipo, o conjunto de métodos de afectação de propriedades seria construído deste modo:

var compiledExpressions = (from property in objectType.GetProperties()
                           let objectParameterExpression = Expression.Parameter(typeof(object), "o")
                           let convertedObjectParameteExpressionr = Expression.ConvertChecked(objectParameter, objectType)
                           let valueParameter = Expression.Parameter(propertyType, "v")
                           let convertedValueParameter = Expression.ConvertChecked(valueParameter, property.PropertyType)
                           let propertyExpression = Expression.Property(convertedObjectParameter, property)
                           select
                                Expression.Lambda<Action<object, object>>(
                                    Expression.Assign(
                                        propertyExpression,
                                        convertedValueParameter
                                    ),
                                    objectParameter,
                                    valueParameter
                                ).Compile()).ToArray();

E hidratar objectos seria algo deste género:

for (int o = 0; o < objects.Length; o++)
{
    var objectProperties = objects[o];

    var newObject = newObjects[o] = Activator.CreateInstance(objectType);

    for (int p = 0; p < compiledExpressions.Length; p++)
    {
        compiledExpressions[p](newObject, objectProperties[p]);
    }
}
Torne-se Um Especialista Em Árvores De Expressões Com O .NET Reflector

No seguimento da minha última entrada, recebi muitas questões acerca de como me tinha tornado um especialista na criação de árvores de expressões.

A resposta é: .NET Reflector

Naquela entrada eu precisava de gerar uma árvore de expressões para esta expressão:

Expression<Func<object, object>> expression = o => ((object)((SomeType)o).Property1);

Simplesmente compiley este código no Visual Studio 2010, carreguei a assembly no .NET Reflector, e desassemblei-a para C# sem optimizações (View –> Options –> Disassembler –> Optimization: None).

O código desassemblado ficou assim:

Expression<Func<object, object>> expression;
ParameterExpression CS$0$0000;
ParameterExpression[] CS$0$0001;
expression = Expression.Lambda<Func<object, object>>(Expression.Convert(Expression.Property(Expression.Convert(CS$0$0000 = Expression.Parameter(typeof(object), "o"), typeof(SomeType)), (MethodInfo) methodof(SomeType.get_Property1)), typeof(object)), new ParameterExpression[] { CS$0$0000 });

Depois de atribuír nomes válidos em C# para as variáveis e arrumar um pouco melhor o código, obtive isto:

ParameterExpression parameter = Expression.Parameter(typeof(object), "o");
Expression<Func<object, object>> expression =
    Expression.Lambda<Func<object, object>>(
        Expression.Convert(
            Expression.Property(
                Expression.Convert(
                    parameter,
                    typeof(SomeType)
                ),
                "Property1"
            ),
            typeof(object)
        ),
        parameter
    );

Fácil! Não é?

Despejando Objectos Usando Árvores De Expressões

LINQ Com C#

Um colega perguntou-me se eu conhecia alguma forma de despejar uma lista de objectos para uma DataTable com melhor performance que a que ele estava a usar.

Os objects a serem despejados têm, geralmente, mais de uma dúzia de propriedades, mas, para os efeitos deste texto, assumamos que se parecem com isto:

class SomeClass
{
    public int Property1 { get; set; }
    public long Property2 { get; set; }
    public string Property3 { get; set; }
    public object Property4 { get; set; }
    public DateTimeOffset Property5 { get; set; }
}

O código que o meu colega estava a usar era algo deste género:

var properties = objectType.GetProperties();

foreach (object obj in objects)
{
    foreach (var property in properties)
    {
        property.GetValue(obj, null);
    }
}

Para uma lsita de um milhão de objectos, isto demora pouco mais de 6000 milissegundos na minha máquina.

Eu pensei logo: Árvores de Expressões!

Se o tipo dos objectos fosse conhecido em tempo de execução, seria tão simples como:

Expression<Func<SomeClass, int>> expression = o => o.Property1;
var compiled = expression.Compile();
var propertyValue = compiled.Invoke(obj);

Mas, em tempo de compilação, o tipo dos objectos e, consequentemente, o tipo das suas propriedades, não é conhecido. Portanto, será necessário, para cada propriedade, uma expressão como esta:

Expression<Func<object, object>> expression = o => ((SomeClass)o).Property1;

A expressão anterior obtem o valor da prorpiedade da conversão do parâmetro do tipo object para o tipo do objecto. O resultando também tem de ser convertido para object porque o tipo do resultado deve corresponder ao do valor de retorno da expressão.

Para o mesmo tipo de objectos, a colecção de acessores de propriedades será obtida do seguinte modo:

var compiledExpressions = (from property in properties
                           let objectParameter = Expression.Parameter(typeof(object), "o")
                           select
                             Expression.Lambda<Func<object, object>>(
                                 Expression.Convert(
                                     Expression.Property(
                                         Expression.Convert(
                                             objectParameter,
                                             objectType
                                         ),
                                         property
                                     ),
                                     typeof(object)
                                 ),
                                 objectParameter
                             ).Compile()).ToArray();

Parece demasiado complicado, mas ler todos as propriedades de todos os objectos para o mesmo conjunto de objectos com o seguinte código:

foreach (object obj in objects)
{
    foreach (var compiledExpression in compiledBLOCKED EXPRESSION
    {
        compiledExpression(obj);
    }
}

demora pouco mais de 150 milissegundos na minha máquina.

Isso mesmo. 2.5% do valor anterior.

C#: Mais Acerca Da Variância De Arrays

Numa entrada anterior entrada, falei sobre como os arrays são covariantes em relação ao tipo dos seus elementos, mas que essa variância não é segura.

No exemplo seguinte, a segunda afectação é inválida em tempo de execução porque, embora o tipo da variável objectArray seja array de object, o verdadeiro tipo do array é array de string e um object não pode ser atribuído a uma string.

object[] objectArray = new string[] { "string 1", "string 2" };
objectArray[0] = "string 3";
objectArray[1] = new object();

Por outro lado, porque os arrays não são contravariantes em relação ao tipo dos seus elementos, no exemplo seguinte, a segunda linha falhará em tempo de execução porque string[] ≤ object[].

object[] objectArray = new object[] { "string 1", "string 2" };
string[] stringArray = (string[])objectArray;

O facto de todos os elementos no array de objects serem strings não faz con que seja convertível num array de string. Para converter este array de objects com strings array de strings, vai ser necessário criar um novo array de strings e copiar todos os elementos convertidos.

Essa operação pode ser tão fácil como isto:

string[] stringArray = objectArray.Cast<string>().ToArray();

O código acima é apenas uma forma abreviada de escrever código para percorrer todo o array e converter os seus elementos para string e criar um array de strings com esses elementos.

Os arrays são uma boa estrutura para armazenar dados porque são a única estrutura que o ambiente de execução nos dá para armazenar grupos de items do mesmo tipo. No entanto, por causa de limitações como a acima, o seu uso em APIs deve ser pensado com muito cuidado.

Se o que é necessário é percorrer todos os elementos de uma colecção, deveria ser usado um IEnumerable<T> (IEnumerable<out T> em .NET 4.0). Deste modo, o custo de usar Enumerable.Cast<T>() é mínimo.

O Estranho Caso Do Computador Que Acordava Sozinho Misteriosamente

Normalmente, quando eu vou dormir, também ponho o meu computador a dormir.

Mas, desde há alguns meses, o computador acordava sozinho e eu não conseguia percber porquê.

Com a ajuda do Pete consegui perceber porquê:

C:\>PowerCfg -LASTWAKE
Wake History Count - 1
Wake History [0]
  Wake Source Count - 1
  Wake Source [0]
    Type: Wake Timer
    Owner: [PROCESS] \Device\HarddiskVolume2\Windows\System32\services.exe
    Owner Supplied Reason: Windows will execute '\Microsoft\Windows\Media Center\mcupdate_scheduled' scheduled task that requested waking the computer.

Usando PowerCfg –LASTWAKE é possível saber o que acordou o computador da última vez que este acordou.

Afinal era apenas uma tarefa agendada do Media Center que acordava o meu computador.

Como Adicionar Ou Remover Instâncias Do SQL Server 2008 / 2008R2

Depois de instalar o Visual Studio 2010, inadvertidamente acabei com o SQL Server 2008 Express instalado, tendo já instalado o SQL Server 2008 Developer. Apenasquando ia actualizar para o SQL Server 2008R2 descobri que tinha duas instancias instalads.

Procurei por todo o lado e não consegui encontrar nenhuma forma de remover a instância SQLEXPRESS.

Apenas quando decidi desinstalar o SQL Server por completo descobri como o fazer.

NoWindows 7, ir a Programs and Features e escolher Microsoft SQL Server 2008 R2 (64-bit) (ou qualquer que seja a versão de SQL Server de que se pretende remover a instância).

Escolher Remove (ou Add, se se pretende adicionar uma instância).

Seguir os passos e escolher a instância que se pretende remover.

Seleccionar as funcionalidades que se pretende remover (seleccionar todas as funcionalidades para remover a instância).
Nota: Não remover as funcionalidades partilhadas se não se pretendem remover outras instâncias

Talvez seja algo que eu devesse saber, mas não sabia e não foi nada fácil encontrar.

Como Resolver O Problema Da Opção “Create Virtual Machine” Em Falta - Windows Virtual PC

Em alguns dos meus sistemas Windows 7, após a instalação do Windows Virtual PC, não tinham a opção Create virtual machine na pasta das Virtual Machines.

O Bob Comer indicou-me esta entrada no Virtual PC Guy's Blog mas, antes de tentar as receitas apresentadas (que envolviam reiniciar a sessão ou o sistema), tentei simplesmente usar a opção Reset Folders na aba View das Folder Options. E o problema foi imediatamente resolvido.

Portanto, se estiverem com o mesmo problema, talvez queiram tentar primeiro esta solução simples.

No final, à cautela, adicionei os seguites atalhos ao menu Iniciar:

  • Windows Virtual PC Wizard

    %SystemRoot%\System32\VPCWizard.exe

  • Windows Virtual PC Settings

    %SystemRoot%\System32\VPCSettings.exe

O Vosso ASP.NET Development Server Não Funciona?

Desde o Visual Studio 2005, que o Visual Studio traz um servidor web de desenvolvimento: o ASP.NET Development Server.

Tenho usado, desde então, este servidor web para projectos de teste simples com o Visual Studio 2005 e o Visual Studio 2008 em Windows XP Professional no laptop do trabalho e em Windows XP Professional, Windows Vista 64bit Ultimate e Windows 7 64bit Ultimate no desktop de casa sem qualquer problema (além do conhecido problema com custom identities, quero dizer).

Quando recebi um novo laptop no trabalho, instalei o Windows Vista 64bit Enterprise e o Visual Studio 2008 e, para grande surpresa minha, oASP.NET Development Server não funcionava.

Comecei a procurar diferenças entre os dois sistemas e as mais notórias eram:

Sistema

Laptop

Desktop

SKU

Windows Vista 64bit Enterprise

Windows Vista 64bit Ultimate

Pertencente a um domínio

SIM

Não

Anti-Vírus

McAffe

ESET

Depois de me certificar que nenhuma política de domínio estava a ser aplicada ao laptop ou ao meu utilizador e de que nada estava a ser registado pelo anti-vírus, as minhas suspeitas penderam para o facto de o laptop estar uma correr uma SKU Enterprise e o desktop estar a correr uma SKU Ultimate. Depois de ter tido alguns problemas com outras aplicações, tinha a certeza de que o problema era a SKU Enterprise, mas nunca encontrei uma solução para o problema. Porque, na altura, não estava a desenvolver para web, deixei estar.

Depois de actualizar para o Windows 7, o problema persistiu. Mas, porque, na altura, não estava a desenvolver para web, deixei estar – outra vez.

Qgora que instalei o Visual Studio 2010, tinha de resolver o problema. Depois de pesquisar alguns forums e alguns blogues que, ou não ofereciam alguma solução, ou ofereciam soluções demaisado complicadas envolvendo, em alguns casos, mexidas no registry, cheguei à conclusão de que a solução até era muito simples.

Quando o Windows Vista é instalado, o ficheiro hosts, de acordo com isto, contem a seguinte definição:

127.0.0.1       localhost
::1             localhost

Isto não era o que eu tinha no meu ficheiro hosts do laptop. O que eu tinha era isto:

#127.0.0.1       localhost
#::1             localhost

Posso ter sido eu mesmo a modificá-lo, mas pelo número de pessoas que encontrei queixando-se do mesmo problema no Windows Vista, provavelmente, era isto mesmo que tinha sido lá deixado pela instalação.

A instalação do Windows 7 deixam o ficheiro hosts com isto:

#127.0.0.1       localhost
#::1             localhost

Apesar do ASP.NET Development Server funcionar correctamente no Windows 7 64bit Ultimate, no Windows 7 64bit Enterprise necessita que o ficheiro hosts tenha a seguinte definição:

127.0.0.1       localhost
::1             localhost

E suspeito que o mesmo seja necessário para o Windows Vista 64bit Enterprise.

Como Detectar As Definições Regionais A Partir Do Web Browser

Recentemente um amigo perguntou-me algo como: “Como é que obtenho as definições regionais de um pedido a um web server?”

Tanto quanto sei, o web browser apenas envia o header HTTP only Accept-Language e nada mais. Pode-se pegar nesta informação e usar as definições regionais por omissão, mas se o utilizador for como eu, estarão erradas.

E qual é o problema de não acertar com as definições regionais do cliente?

Se se estivermos apenas a gerar HTML e mantivermos a coerência, não há qualquer problema. Mas, e se o utilizador quiser copiar valores numéricos e/ou datas/horas para, por exemplo, um folha Excel? Ou se se quiser exportar os dados para um ficheiro em formato CSV?

Uma solução

Consultando a JScript Language Reference, descobri que tanto Number com Date têm métodos toString relacionados com locale e comecei a experimentá-los.

Definições de formatos numéricos

Para os formatos numéricos, primeiro necessitamos de um número que se comporte de forma previsíve em com qualquer cultura (“qualquer cultura” significa “qualquer cultura que eu conheça”) e todas as definições possíveis e convertê-lo para string usando o método toLocaleString:

var number = 111111111.111111111;
var numberString = number.toLocaleString();

(Com as minhas definições regionais, numberString terá o valor 111 111 111.11)

Para obter o separador decimal, temos de obter o primeiro carácter, a partir do fim, que não seja 1:

var decimalSeparator;
var decimalDigits;
for (var i = numberString.length - 1; i >= 0; i--) {
    var char = numberString.charAt(i);
    if (char != "1") {
        decimalSeparator = char;
        decimalDigits = numberString.length - i - 1;
        break;
    }
}

Se se contarem quantos 1s saltámos, obtme-se o número de casas décimais.

Do mesmo modo, o primeiro carácter, a contar do início, que não seja 1 é o separador de grupos:

var groupSeparator;
for (var i = 0; i < numberString.length; i++) {
    var char = numberString.charAt(i);
    if (char != "1") {
        groupSeparator = char;
        break;
    }
}

Tendo obitod o separador de grupos, podemos obter a composição dos mesmos (podem não ter todos o mesmo número de dígitos):

var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
for (g in digitGrouping) {
    digitGrouping[g] = digitGrouping[g].length;
}
Definições de data e hora

Os valores de data e hora são mais difíceis de interpretar e podem não ser necessárias todas as definições, por isso, vou-vos deixar este como exercício:

var dateTime = new Date(9999, 11, 31, 23, 30, 45);
dateTimeString = dateTime.toLocaleString();
Definições de listas

A última definição é a do separador de listas (muito útil para os tais ficheiros CSV):

var list = ["a", "b"];
listSeparator = list.toLocaleString().substring(1, 2);

Página de testes

Aqui está uma página de testes para testar todas estas definições:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Test Page</title>
    <style type="text/css">
        label  { width: 8em; text-align: right; padding-right: 0.5em; white-space: nowrap; }
        span { border: 1px solid; white-space: nowrap; }
    </style>
    <script type="text/javascript">
        function init() {
            document.all.userLanguage.innerText = window.navigator.userLanguage;
            document.all.systemLanguage.innerText = window.navigator.systemLanguage;

            // Decimal separator and decimal digits
            var number = 111111111.111111111;
            var numberString = (111111111.111111111).toLocaleString();

            var decimalSeparator;
            var decimalDigits;
            for (var i = numberString.length - 1; i >= 0; i--) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    decimalSeparator = char;
                    decimalDigits = numberString.length - i - 1;
                    break;
                }
            }
            document.all.decimalSeparator.innerText = decimalSeparator;
            document.all.decimalDigits.innerText = decimalDigits;

            // Digit grouping separator and digit goups
            var groupSeparator;
            for (var i = 0; i < numberString.length; i++) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    groupSeparator = char;
                    break;
                }
            }
            document.all.groupSeparator.innerText = groupSeparator;

            var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
            for (g in digitGrouping) {
                digitGrouping[g] = digitGrouping[g].length;
            }
            document.all.digitGrouping.innerText = digitGrouping.toString();

            // Date format
            var dateTime = new Date(9999, 11, 31, 23, 30, 45);
            var dateTimeString = dateTime.toLocaleString();
            document.all.dateTimeFormat.innerText = dateTimeString;

            // List separator
            var list = ["a", "b"];
            var listSeparator = list.toLocaleString().substring(1, 2);
            document.all.listSeparator.innerText = listSeparator;
        }
    </script>
</head>
<body onload="init()">
    <p><label for="userLanguage">User language:</label><span id="userLanguage"></span></p>
    <p><label for="systemLanguage">System language:</label><span id="systemLanguage"></span></p>
    <p><label for="decimalSeparator">Decimal separator:</label><span id="decimalSeparator"></span></p>
    <p><label for="decimalDigits">Decimal digits:</label><span id="decimalDigits"></span></p>
    <p><label for="groupSeparator">Digit separator:</label><span id="groupSeparator"></span></p>
    <p><label for="digitGrouping">Digit grouping:</label><span id="digitGrouping"></span></p>
    <p><label for="dateTimeFormat">Date/Time format:</label><span id="dateTimeFormat"></span></p>
    <p><label for="listSeparator">List separator:</label><span id="listSeparator"></span></p>
</body>
</html>
Novo Livro Do Luís Abreu: ASP.NET 4.0 – Curso Completo
ASP.NET 4.0 – Curso Completo

Este livro, com vários exemplos práticos, apresenta as principais características relacionadas com a construção de aplicações Web através da nova plataforma da Microsoft, ASP.NET 4.0. Inicia com a apresentação da framework que serve de suporte ao desenvolvimento de páginas (Web forms e ASP.NET server controls simples) e introduz gradualmente todas as novas funcionalidades disponibilizadas.

Mais compacta do que as suas antecessoras (parte do conteúdo foi movido para o para o sítio da FCA na forma de apêndices), esta nova obra, em que foi dado ênfase às novas funcionalidades da recente versão 4.0, tem como objectivo ensinar o programador que se está a iniciar na plataforma ASP.NET, sendo também uma ferramenta indispensável para o programador conhecedor da framework ASP.NET que pretenda fazer a transição para a nova versão.

Desta vez há boas notícias para os leitures Brasileiros. O livro vai ser distribuído no Brasil por:

Zamboni Comércio de Livros Ltda.
Av.Parada Pinto, 1476
São Paulo – SP
Telf. / Fax: +55 11 2233-2333
E-mail: zambonibooks@terra.com.br

LINQ Com C#

O nosso livro (LINQ Com C#) ainda não é distribuído por este distribuidor mas podem sempre contactá-lo para ver se é possível obtê-lo através dele.

TechDays 2010: As Novidades Do C# 4.0

Gostaria de agradecer a quem esteve presente na minha sessão no TechDays 2010 e espero que tenha conseguido passar a mensagem das novidades da última versão da linguagem C#.

Para quem não esteve na sessão (ou esteve e quiser rever o conteúdo), a apresentação pode ser descarregada daqui.

Os exemplos de código podem ser descarregados daqui.

Aqui fica também a lista dos recursos mencionados na apresentação:

C# 4.0: Melhoramentos Para Interoperabilidade Com COM

A resolução dinâmica e argumentos com nome e opcionais melhoram largamente a experiência de interoperar com APIs COM como as Office Automation Primary Interop Assemblies (PIAs). Mas, para aliviar ainda mais o desenvolvimento de interoperabilidade com COM, foram adicionados algumas funcionalidades específicas para COM ao C# 4.0.

Omissão do Modificador ref

Devido ao diferente modelo de programação, muitas APIs COM contêm muitos parâmetros por referência. A intenção deste parâmetros não é modificar os argumentos que lhes são passados, mas apenas outro modo de passar valores.

Especificamente com métodos COM, o compilador permite declarar a chamada ao método passando os argumentos por valor e gerará automaticamente variávias temporárias para manter os valores por forma a poder passá-los por referência e descartará os seus valores após o retorno da chamad ao método. Do ponto de vista do porgramador, os argumentos são passados por valor.

Sendo assim, esta chamada:

object fileName = "Test.docx";
object missing = Missing.Value;

document.SaveAs(ref fileName,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing);

pode ser escrita assim:

document.SaveAs("Test.docx",
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value);

E porque todos os parâmteros que recebem o valor Missing.Value têm esse valor como valor por omissão, a declaração da chamada ao método pode ser reduzida a:

document.SaveAs("Test.docx");

Importação Dinâmica

Muitos métodos COM aceitam e retornam tipos variant, que são representados nas PIAs como object. Na grande maioria dos casos, o programador que está a chamar estes métodos conheço o tipo estático dos objectos que estão a ser retornados pelo contexto da chamada, mas ainda são obrigados a explicitamente converter o valor retornado para fazer uso desse seu conhecimento. Estas conversões são tão comuns que se tornam um incómodo.

Para tornar a vida do programador mais fácil, é agora possível importar as APIs COM de modo a que os variants seja representados usando o tipo dynamic o que quer dizer que as assinaturas COM têm agora ocorrências de dynamic em vez de object.

Isto quer dizer que membros de um objecto retornado podem agora ser facilemtne acedidos ou atribuídos a variáveis fortemente tipadas sem necessiade de conversão explícita.

Em vez deste código:

((Excel.Range)(excel.Cells[1, 1])).Value2 = "Hello World!";

pode ser usado este:

excel.Cells[1, 1] = "Hello World!";

E em vez deste:

Excel.Range range = (Excel.Range)(excel.Cells[1, 1]);

pode ser usado este:

Excel.Range range = excel.Cells[1, 1];

Propriedades Indexadas E Por Omissão

Algumas funcionalidades das interfaces COM ainda não estão disponíveis em C#. No topo desta lista estão as propriedades indexadas e as propriedades por omissão. Como mencionado anteriormente, estas poderão ser usadas se a interface COM for acedida dinâmicamente, mas não serão reconhecidas por código C# estáticamente tipoado.

No PIAs – Equivalênica De Tipos E Tipos Embebidos

Para assemblies indentificadas com PrimaryInteropAssemblyAttribute, o compilador criará tipos equivalentes (interfaces, estruturas, enumerados e delegates) e embebê-los-á na assembly gerada.

Para reduzir a dimensão final da assembly gerada, apenas os tipos e seus membros usados serão gerados e embebidos.

Embora isto torne o desenvolvimento e distribuição de aplicações que usam os componentes COM mais fácil porque não há necessidade de distribuír as PIAs, quem desenvolve os componentes COM continua a ter de desenvolver as PIAs.

C# 4.0: Programa&#231;&#227;o Din&#226;mica

A principal funcionalidade do C# 4.0 é a programação dinâmica. Não apenas em termos de tipos dinâmicos, mas, un sentido mais lato, a capacidade de falar com qualquer coisa que não seja staicamente tipada para ser um objecto .NET.

Dynamic Language Runtime

O Ambiente de Execução para Linguagens Dinâmicas (Dynamic Language Runtime - DLR) é um pedaço de tecnologia que unifica a programação dinâmica na plataforma .NET, da mesma forma que o Ambiente de Execução Comum para Linguagens (Common Language Runtime - CLR) tem sido a plataforma comum para linguagens estaticamente tipadas.

A CLR sempre teve capacidedes dinâmicas. Sempre foi possível usar reflecção, mas o seu prinicpal objectivo nunca foi ser um ambiente de programação dinamido e faltava-lhe algumas funcionalidades. A DLR assenta na CLR e adiciona essas funcionalidades em falta à plataforma .NET.

Dynamic Language Runtime

A DLR é a infra-estrutura base que consiste em:

  • Dynamic Dispatch

    As mesmas árvores de expressões usedadas no LINQ, agora melhoradas para suportar instruções.

  • Dynamic Dispatch

    Despacha as invocações para o binder apropriado.

  • Call Site Caching

    Para melhor eficiência.

As linguagens dinâmicas e as linguagens dinâmicas assentam na DLR. O IronPython e o IronRuby já assentavam na DLR, e agora, o suporte para usar a DLR foi adicionado ao C# e ao Visual Basic. Espera-se que outras linguagens construídas sobre a CLR passem também, no futuro, a usar a DLR.

Sob a DLR encontram-se os binders que falam com uma variedade de diferentes tecnologias:

  • .NET Binder

    Permite falar com objectos .NET.

  • JavaScript Binder

    Permite falar com JavaScript em SilverLight.

  • IronPython Binder

    Permite falar com IronPython.

  • IronRuby Binder

    Permite falar com IronRuby.

  • COM Binder

    Permite falar com COM.

Com todos estes binders é possível ter uma única experiência de programação para falar com todos estes ambientes que não são objectos .NET estaticamente tipados.

O Tipo Estático dynamic

Tomemos este tradicional código estaticamente tipado:

Calculator calculator = GetCalculator();
int sum = calculator.Sum(10, 20);

Porque a variável que recebe o valor de retorno do método GetCalulator é estaticamente tipada para ser do tipo Calculator e, porque o tipo Calculator tem um método Add que recebe dois inteiros e retorna um inteiro, é possível chamar esse método Sum e atribuír o seu valor de retorno a uma variável etaticamente tipada como inteiro.

Agora suponhamos que a calculadora não era uma classe .NET estaticamente tipada, mas, em vez disso, um objecto COM ou algum códiogo .NET de que não conhecemos o tipo. De repente tornou-se muito penoso chamar o método Add:

object calculator = GetCalculator();
Type calculatorType = calculator.GetType();
object res = calculatorType.InvokeMember("Add", BindingFlags.InvokeMethod, null, calculator, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

E se a calculador for um objecto JavaScript?

ScriptObject calculator = GetCalculator();
object res = calculator.Invoke("Add", 10, 20);
int sum = Convert.ToInt32(res);

Para cada domínio dinâmico temos uma experiência de programação diferente o que torna a unificação do código muito difícil.

Com o C# 4.0 é agora possível escrever código como este:

dynamic calculator = GetCalculator();
int sum = calculator.Add(10, 20);

Basta declarar uma variável cujo tipo estático é dynamic. dynamic é uma pseudo palavra reservada (como var) que indica ao compilador que a resolução das operações sobre calculator será efectuada de forma dinâmica.

Deve-se encarar um dynamic como um object (System.Object) com semântica dinâmica associada. Qualquer coisa pode ser afectada a dynamic.

dynamic x = 1;
dynamic y = "Hello";
dynamic z = new List<int> { 1, 2, 3 };

Em tempo de execução, todos os objectos terão um tipo. No exemplo acima, No exemplo precedente, em tempo de execução, x é do tipo System.Int32.

Quando um ou mais operandos de uma operação são tipados dynamic, a selecção do método a chamar é deferida para tempo de execução ao invés de ser feita em tempo de compilação. Então o tipo em tempo de execução é substituído em todas as variáveis e a resolução de sobreposições (overloads) ocorre, tal como em tempo de com ocorreria em tempo de compilação.

O resultado de qualquer operação dinâmtica é sempre dynamic e, quando um objecto dynamic é atribuído a algo, ocorre uma conversão dinâmica.

Código Resolução Método
double x = 1.75;
double y = Math.Abs(x);

Tempo de compilação

double Abs(double x)

dynamic x = 1.75;
dynamic y = Math.Abs(x);

Tempo de execução

double Abs(double x)

dynamic x = 2;
dynamic y = Math.Abs(x);     

Tempo de execução

int Abs(int x)

O código acima será sempre fortemente tipado. A diferença é que, no primeiro caso a resolução do método é feita m tempo de compilação, e nos outros em tempo de execução.

IDynamicMetaObjectObject

A está DLR pré-preparada para conhecer objectos .NET, ojectos COM mas qualquer linguagem dinâmica pode implementar os seus próprios objectos dinãmicos, assim como qualquer programador pode implementar os seus objectos dinâmicos em C# através da implementação da interface IDynamicMetaObjectProvider. Quando um objecto implementa IDynamicMetaObjectProvider, pode participar na resolição de como os métodos são chamados ou as propriedaes acedidas.

A .NET Framework já contem duas implementações de IDynamicMetaObjectProvider:

  • DynamicObject : IDynamicMetaObjectProvider

    A classe DynamicObject permite definir que operações podem ser executadas em objectos dinâmicos e como serão executadas essas operações. Por exemplo, pode-se definir o que acontece quando se tenta ler ou escreve uma propriedade, chamar um método ou efectuar operações matemática como adições e multiplicações.

  • ExpandoObject : IDynamicMetaObjectProvider

    A classe ExpandoObject permite adicionar e remover membros às suas instâncias em tempo de execução e também obter os valores desses membros. Esta classe suporta a ligação dinâmica que permite usar um sintaxe normal como sampleObject.sampleMember, em vez de uma sintaxe mais complexa como  sampleObject.GetAttribute("sampleMember").

More Posts Next page »