Pergunta

Embora a especificação C# inclua um pré-processador e diretivas básicas (#define, #if, etc), a linguagem não possui o mesmo pré-processador flexível encontrado em linguagens como C/C++.Acredito que a falta de um pré-processador tão flexível foi uma decisão de design tomada por Anders Hejlsberg (embora, infelizmente, não consiga encontrar referência a isso agora).Por experiência própria, esta é certamente uma boa decisão, já que havia algumas macros realmente terríveis e insustentáveis ​​criadas quando eu estava fazendo muito C/C++.

Dito isso, há vários cenários em que eu poderia achar útil um pré-processador um pouco mais flexível.Código como o seguinte poderia ser melhorado por algumas diretivas simples de pré-processador:

public string MyProperty
{
  get { return _myProperty; }
  set
  {
    if (value != _myProperty)
    {
      _myProperty = value;
      NotifyPropertyChanged("MyProperty");
      // This line above could be improved by replacing the literal string with
      // a pre-processor directive like "#Property", which could be translated
      // to the string value "MyProperty" This new notify call would be as follows:
      // NotifyPropertyChanged(#Property);
    }
  }
}

Seria uma boa ideia escrever um pré-processador para lidar com casos extremamente simples como este?Steve McConnell escreveu em Código completo (pág.208):

Escreva seu próprio pré-processador Se uma linguagem não inclui um pré-processador, é bastante fácil escrever um...

Eu estou rasgado.Foi uma decisão de design deixar um pré-processador tão flexível fora do C#.No entanto, um autor que respeito muito menciona que pode ser aceitável em algumas circunstâncias.

Devo construir um pré-processador C#?Existe algum disponível que faça as coisas simples que eu quero fazer?

Foi útil?

Solução

Considere dar uma olhada em uma solução orientada a aspectos como PostSharp, que injeta código após o fato com base em atributos personalizados.É o oposto de um pré-compilador, mas pode fornecer o tipo de funcionalidade que você procura (notificações de PropertyChanged, etc.).

Outras dicas

Devo construir um pré-processador C#?Existe algum disponível que faça as coisas simples que eu quero fazer?

Você sempre pode usar o pré-processador C - C# é próximo o suficiente em termos de sintaxe.M4 também é uma opção.

Eu sei que muitas pessoas pensam que código curto é igual a código elegante, mas isso não é verdade.

O exemplo que você propõe está perfeitamente resolvido em código, como você mostrou, para que você precisa de uma diretiva de pré-processador?Você não deseja "pré-processar" seu código, deseja que o compilador insira algum código para você em suas propriedades.É um código comum, mas esse não é o propósito do pré-processador.

Com o seu exemplo, onde você coloca o limite?É claro que isso satisfaz um padrão de observador e não há dúvida de que será útil, mas há muitas coisas que seriam úteis e que são realmente feitas porque o código fornece flexibilidade onde o pré-processador não.Se você tentar implementar padrões comuns através de diretivas de pré-processador, você terminará com um pré-processador que precisa ser tão poderoso quanto a própria linguagem.Se você quiser processe seu código de uma maneira diferente usando uma diretiva de pré-processador mas se você quiser apenas um trecho de código, encontre outra maneira, porque o pré-processador não foi feito para fazer isso.

Usando um pré-processador estilo C++, o código do OP pode ser reduzido a esta linha:

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY ficaria mais ou menos assim:

#define OBSERVABLE_PROPERTY(propType, propName) \
private propType _##propName; \
public propType propName \
{ \
  get { return _##propName; } \
  set \
  { \
    if (value != _##propName) \
    { \
      _##propName = value; \
      NotifyPropertyChanged(#propName); \
    } \
  } \
}

Se você tiver 100 propriedades para lidar, isso significa aproximadamente 1.200 linhas de código vs.~100.O que é mais fácil de ler e entender?O que é mais fácil de escrever?

Com C#, supondo que você recorte e cole para criar cada propriedade, são 8 colas por propriedade, 800 no total.Com a macro, não é necessário colar nada.Qual tem maior probabilidade de conter erros de codificação?O que é mais fácil de alterar se você precisar adicionar, por exemplo.uma bandeira IsDirty?

As macros não são tão úteis quando é provável que haja variações personalizadas em um número significativo de casos.

Como qualquer ferramenta, as macros podem ser abusadas e podem até ser perigosas nas mãos erradas.Para alguns programadores, esta é uma questão religiosa, e os méritos de uma abordagem em detrimento de outra são irrelevantes;se for você, você deve evitar macros.Para aqueles de nós que usam ferramentas extremamente precisas de maneira regular, habilidosa e segura, as macros podem oferecer não apenas um ganho imediato de produtividade durante a codificação, mas também no downstream durante a depuração e a manutenção.

O principal argumento contra a construção de um pré-processador para C# é a integração no Visual Studio:seriam necessários muitos esforços (se possível) para que o intellisense e a nova compilação de plano de fundo funcionassem perfeitamente.

As alternativas são usar um plugin de produtividade do Visual Studio como ReSharper ou CódigoRush.Este último tem - até onde sei - um sistema de templates incomparável e vem com um excelente reestruturação ferramenta.

Outra coisa que pode ser útil para resolver os tipos exatos de problemas aos quais você está se referindo é uma estrutura AOP como PostSharp.
Você pode então usar atributos customizados para adicionar funcionalidades comuns.

Para obter o nome do método atualmente executado, você pode observar o rastreamento de pilha:

public static string GetNameOfCurrentMethod()
{
    // Skip 1 frame (this method call)
    var trace = new System.Diagnostics.StackTrace( 1 );
    var frame = trace.GetFrame( 0 );
    return frame.GetMethod().Name;
}

Quando você está em um método de conjunto de propriedades, o nome é set_Property.

Usando a mesma técnica, você também pode consultar o arquivo de origem e as informações de linha/coluna.

No entanto, não fiz benchmarking disso, criar o objeto stacktrace uma vez para cada conjunto de propriedades pode ser uma operação muito demorada.

Acho que possivelmente você está perdendo uma parte importante do problema ao implementar o INotifyPropertyChanged.Seu consumidor precisa de uma forma de determinar o nome da propriedade.Por esta razão você deve ter seus nomes de propriedades definidos como constantes ou strings estáticas somente leitura, desta forma o consumidor não precisa `adivinhar' os nomes das propriedades.Se você usasse um pré-processador, como o consumidor saberia qual é o nome da string da propriedade?

public static string MyPropertyPropertyName
public string MyProperty {
    get { return _myProperty; }
    set {
        if (!String.Equals(value, _myProperty)) {
            _myProperty = value;
            NotifyPropertyChanged(MyPropertyPropertyName);
        }
    }
}

// in the consumer.
private void MyPropertyChangedHandler(object sender,
                                      PropertyChangedEventArgs args) {
    switch (e.PropertyName) {
        case MyClass.MyPropertyPropertyName:
            // Handle property change.
            break;
    }
}

Se eu estivesse projetando a próxima versão do C#, pensaria que cada função teria uma variável local incluída automaticamente contendo o nome da classe e o nome da função.Na maioria dos casos, o otimizador do compilador o eliminaria.

Não tenho certeza se há muita demanda por esse tipo de coisa.

@Jorge escreveu:Se você quiser processar seu código de uma maneira diferente, use uma diretiva de pré-processador, mas se você quiser apenas um trecho de código, encontre outra maneira, porque o pré-processador não foi feito para fazer isso.

Interessante.Eu realmente não considero que um pré-processador funcione necessariamente dessa maneira.No exemplo fornecido, estou fazendo uma simples substituição de texto, que está alinhada com a definição de um pré-processador em Wikipédia.

Se este não for o uso adequado de um pré-processador, como deveríamos chamar uma simples substituição de texto, que geralmente precisa ocorrer antes de uma compilação?

Pelo menos para o cenário fornecido, há uma solução mais limpa e de tipo seguro do que construir um pré-processador:

Use genéricos.Igual a:

public static class ObjectExtensions 
{
    public static string PropertyName<TModel, TProperty>( this TModel @this, Expression<Func<TModel, TProperty>> expr )
    {
        Type source = typeof(TModel);
        MemberExpression member = expr.Body as MemberExpression;

        if (member == null)
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a method, not a property",
                expr.ToString( )));

        PropertyInfo property = member.Member as PropertyInfo;

        if (property == null)
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a field, not a property",
                expr.ToString( )));

        if (source != property.ReflectedType ||
            !source.IsSubclassOf(property.ReflectedType) ||
            !property.ReflectedType.IsAssignableFrom(source))
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a property that is not a member of type '{1}'.",
                expr.ToString( ),
                source));

        return property.Name;
    }
}

Isso pode ser facilmente estendido para retornar um PropertyInfo em vez disso, permitindo que você obtenha muito mais coisas do que apenas o nome da propriedade.

Já que é um Extension method, você pode usar esse método em praticamente todos os objetos.


Além disso, isso é seguro para o tipo.
Não posso enfatizar isso o suficiente.

(Eu sei que é uma questão antiga, mas achei que faltava uma solução prática.)

Se você está pronto para abandonar o C#, você pode querer dar uma olhada no Vaia linguagem que tem incrivelmente flexível macro suporte através AST (Árvore de Sintaxe Abstrata).É realmente ótimo se você puder abandonar a linguagem C#.

Para obter mais informações sobre Boo, consulte estas questões relacionadas:

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top