C # tem propriedades de extensão?


769

C # tem propriedades de extensão?

Por exemplo, posso adicionar uma propriedade de extensão DateTimeFormatInfochamada ShortDateLongTimeFormatque retornaria ShortDatePattern + " " + LongTimePattern?


14
Eu queria adicionar um método de extensão chamado IsNull em Nullable <T> que retornaria apenas! HasValue. .IsNull () é definitivamente menos bonita do que .IsNull
Ken

1
Acho que isso é útil para o operador ?
trinário

2
Eu queria que isso imitasse os Java's, enumque podem ter propriedades e métodos. Os c # enumnão podem ter propriedades ou métodos, mas você pode criar métodos de extensão neles. Esta pergunta foi útil para mim e não deve ser encerrada.
precisa saber é o seguinte

Embora, como muitas pessoas tenham dito, não haja planos atualmente para adicionar isso ao idioma, não há razão para que isso não possa ser feito. O fato de o F # não ter apenas propriedades de extensão, mas extensões estáticas também para mim prova que é pelo menos uma boa ideia.
precisa saber é o seguinte

2
Deve haver um
Rootel 6/16

Respostas:


366

No momento, ele ainda não é suportado imediatamente pelo compilador Roslyn ...

Até agora, as propriedades da extensão não eram vistas como valiosas o suficiente para serem incluídas nas versões anteriores do padrão C #. O C # 7 e o C # 8.0 viram isso como defensor da proposta, mas ainda não foi lançado, principalmente porque, mesmo que já exista uma implementação, eles querem fazê-lo desde o início.

Mas vai ...

Há um item de membros de extensão na lista de trabalho do C # 7, portanto ele pode ser suportado no futuro próximo. O status atual da propriedade de extensão pode ser encontrado no Github, no item relacionado .

No entanto, há um tópico ainda mais promissor, que é o "estender tudo", com foco principalmente nas propriedades e nas classes estáticas ou mesmo nos campos.

Além disso, você pode usar uma solução alternativa

Conforme especificado neste artigo , você pode usar o TypeDescriptorrecurso para anexar um atributo a uma instância de objeto em tempo de execução. No entanto, ele não está usando a sintaxe das propriedades padrão.
É um pouco diferente do açúcar sintático, acrescentando a possibilidade de definir uma propriedade estendida
string Data(this MyClass instance)como um alias para o método de extensão,
string GetData(this MyClass instance)pois ele armazena dados na classe.

Espero que o C # 7 forneça uma extensão com todos os recursos (propriedades e campos); no entanto, nesse ponto, apenas o tempo dirá.

E sinta-se à vontade para contribuir, pois o software de amanhã virá da comunidade.

Atualização: agosto de 2016

Como a equipe dotnet publicou o que há de novo no C # 7.0 e a partir de um comentário de Mads Torgensen :

Propriedades da extensão: um estagiário (brilhante!) As implementou durante o verão como um experimento, junto com outros tipos de membros da extensão. Continuamos interessados ​​nisso, mas é uma grande mudança e precisamos nos sentir confiantes de que vale a pena.

Parece que as propriedades de extensão e outros membros ainda são bons candidatos para serem incluídos em uma versão futura do Roslyn, mas talvez não a versão 7.0.

Atualização: maio de 2017

Os membros da extensão foram encerrados como duplicado do problema de extensão de tudo que também foi fechado. A discussão principal foi de fato sobre a extensibilidade do tipo em um sentido amplo. O recurso agora é rastreado aqui como uma proposta e foi removido do marco 7.0 .

Atualização: agosto de 2017 - recurso proposto em C # 8.0

Embora continue sendo apenas um recurso proposto , agora temos uma visão mais clara do que seria sua sintaxe. Lembre-se de que essa também será a nova sintaxe para os métodos de extensão:

public interface IEmployee 
{
    public decimal Salary { get; set; }
}

public class Employee
{
    public decimal Salary { get; set; }
}

public extension MyPersonExtension extends Person : IEmployee
{
    private static readonly ConditionalWeakTable<Person, Employee> _employees = 
        new ConditionalWeakTable<Person, Employee>();


    public decimal Salary
    {
        get 
        {
            // `this` is the instance of Person
            return _employees.GetOrCreate(this).Salary; 
        }
        set 
        {
            Employee employee = null;
            if (!_employees.TryGetValue(this, out employee)
            {
                employee = _employees.GetOrCreate(this);
            }
            employee.Salary = value;
        }
    }
}

IEmployee person = new Person();
var salary = person.Salary;

Semelhante às classes parciais, mas compilado como uma classe / tipo separado em um assembly diferente. Observe que você também poderá adicionar membros e operadores estáticos dessa maneira. Como mencionado no podcast de Mads Torgensen , a extensão não terá nenhum estado (portanto, não pode adicionar membros da instância privada à classe), o que significa que você não poderá adicionar dados de instância privada vinculados à instância . A razão invocada para isso é que implicaria gerenciar dicionários internamente e poderia ser difícil (gerenciamento de memória, etc ...). Para isso, você ainda pode usar a técnica TypeDescriptor/ ConditionalWeakTabledescrita anteriormente e, com a extensão da propriedade, a oculta sob uma propriedade legal.

A sintaxe ainda está sujeita a alterações, pois implica esse problema . Por exemplo, extendspoderia ser substituído pelo forqual alguns podem parecer mais naturais e menos relacionados ao java.

Atualização dezembro de 2018 - Funções, extensões e membros da interface estática

A extensão de tudo não chegou ao C # 8.0, devido a algumas desvantagens explicadas como o final desse ticket do GitHub . Então, houve uma exploração para melhorar o design. Aqui , Mads Torgensen explica quais são os papéis e extensões e como eles diferem:

As funções permitem que as interfaces sejam implementadas em valores específicos de um determinado tipo. As extensões permitem que interfaces sejam implementadas em todos os valores de um determinado tipo, dentro de uma região específica do código.

Isso pode ser visto em uma divisão da proposta anterior em dois casos de uso. A nova sintaxe para extensão seria assim:

public extension ULongEnumerable of ulong
{
    public IEnumerator<byte> GetEnumerator()
    {
        for (int i = sizeof(ulong); i > 0; i--)
        {
            yield return unchecked((byte)(this >> (i-1)*8));
        }
    }
}

então você seria capaz de fazer isso:

foreach (byte b in 0x_3A_9E_F1_C5_DA_F7_30_16ul)
{
    WriteLine($"{e.Current:X}");
}

E para uma interface estática :

public interface IMonoid<T> where T : IMonoid<T>
{
    static T operator +(T t1, T t2);
    static T Zero { get; }
}

Adicionar uma propriedade de extensão em inte tratar o intcomo IMonoid<int>:

public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}

58
Esta é uma das respostas mais úteis que já segui no StackExchange. Atualizando constantemente com o status e mantendo todos informados sobre o assunto, fornecendo links sólidos para discussão e história.
bdrelling

25
É ótimo que você esteja mantendo isso atualizado - obrigado
David Thielen

1
Infelizmente, neste comentário, funções, extensões e membros de interface estática são sinalizados apenas para C # 11 :(
Ian Kemp

436

Não, eles não existem no C # 3.0 e não serão adicionados no 4.0. Está na lista de recursos desejados para C #, para que possa ser adicionado em uma data futura.

Neste ponto, o melhor que você pode fazer é os métodos de extensão no estilo GetXXX.


3
Da mesma forma com propriedades genéricas: você precisa usar a sintaxe 'GetXXX <>'.
Jay Bazuzi

3
ok, foi o que eu pensei. @ Jay, sim, eu também odeio isso, hehe. Especialmente a incapacidade de ter um indexador genérico ... suspiro
Svish

75
Link para a lista de recursos que deseja?
Dan Esparza

2
E as versões 6.0 e 7.0?
Falk

2
Alguma atualização a partir de 2020?
Chad

265

Não, eles não existem.

Eu sei que a equipe de C # estava considerando-os em um ponto (ou pelo menos Eric Lippert estava) - junto com construtores e operadores de extensão (eles podem levar um tempo para se entender, mas são legais ...) No entanto, eu não vi alguma evidência de que eles farão parte do C # 4.


EDIT: Eles não apareceram no C # 5 e, em julho de 2014, não parece que também esteja no C # 6.

Eric Lippert , desenvolvedor principal da equipe de compiladores C # da Microsoft até novembro de 2012, escreveu sobre isso em outubro de 2009:


2
Sim, e eles ainda podem ocultar o campo - definir uma única propriedade pode definir duas propriedades abaixo ou vice-versa. (Imagine algo com uma propriedade normal Size e propriedades de extensão Width / Height, ou vice-versa.) Eles seriam mais úteis como somente leitura, eu suspeito.
9139 Jon Skeet

23
Você não pode vincular aos métodos de extensão ... poder adicionar suas próprias propriedades para a ligação de dados pode ser útil em muitas situações.
Nick

3
@leppie - O valor das extensões de propriedades beneficiaria as propriedades bool e string o máximo que eu acho. Livrar-se do ()final é muito mais legível. Eu sei pessoalmente, pelo menos 90% das extensões que escrevo são desses dois tipos.
Code Maverick

4
Para dar um exemplo de por que isso seria útil, eu tenho um modelo EFCF. Em algumas das classes, tenho propriedades somente leitura que utilizo para retornar informações formatadas: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Gostaria de adicionar mais dessas propriedades, mas não quero "sujar" as classes reais. Nesse caso, uma propriedade de extensão, somente leitura, é perfeita porque eu posso adicionar a funcionalidade, manter a classe principal limpa e ainda expor as informações que quero expor na interface do usuário.
Gup3rSuR4c

4
@ JonSkeet: Você está certo, acabei fazendo o que queria criando minha própria classe, agrupando todos os métodos e propriedades relevantes da classe selada e fornecendo static implicit operator FileInfo(FileInfoEx fex)quais retornos do meu objeto FileInfo contido. Isso efetivamente me permite tratar o FileInfoEx como se ele fosse herdado do FileInfo, mesmo que essa classe esteja selada.
Steve L

27

Atualização (obrigado a @chaost por apontar esta atualização):

Mads Torgersen: "A extensão de tudo não chegou ao C # 8.0. Foi apanhada, se você desejar, em um debate muito empolgante sobre o futuro futuro da linguagem, e agora queremos garantir que não o façamos. adicione-o de uma maneira que iniba essas possibilidades futuras. Às vezes, o design da linguagem é um jogo muito longo! "

Fonte: seção de comentários em https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Parei de contar quantas vezes, ao longo dos anos, abri esta questão com a esperança de ver isso sendo implementado.

Bem, finalmente todos podemos nos alegrar! A Microsoft apresentará isso em seu próximo lançamento do C # 8.

Então, ao invés de fazer isso ...

public static class IntExtensions
{
   public static bool Even(this int value)
   {
        return value % 2 == 0;
   }
}

Finalmente seremos capazes de fazê-lo assim ...

public extension IntExtension extends int
{
    public bool Even => this % 2 == 0;
}

Fonte: https://blog.ndepend.com/c-8-0-features-glimpse-future/


3
Esta semana , os recursos do C # 8.0 foram anunciados e infelizmente não vi nada disso.
Mateo Torres-Ruiz

1
@ MateoTorres-Ruiz Um comentário de 'Mads Torgersen' (C # dev), respondendo a alguém perguntando sobre isso (3 dias atrás): "A extensão de tudo não chegou ao C # 8.0. Foi" pego ", se você quiser , em um debate muito empolgante sobre o futuro futuro da linguagem, e agora queremos garantir que não a adicionemos de maneira a inibir essas possibilidades futuras. Às vezes, o design da linguagem é um jogo muito longo! " Se sente mal .. (Leia isso em ligação Korayems, na seção de comentários)
Chaost

8

Como o @Psyonity mencionou, você pode usar o condicionalWeakTable para adicionar propriedades aos objetos existentes. Combinado com o dinâmico ExpandoObject, você pode implementar propriedades de extensão dinâmica em algumas linhas:

using System.Dynamic;
using System.Runtime.CompilerServices;

namespace ExtensionProperties
{
    /// <summary>
    /// Dynamically associates properies to a random object instance
    /// </summary>
    /// <example>
    /// var jan = new Person("Jan");
    ///
    /// jan.Age = 24; // regular property of the person object;
    /// jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;
    ///
    /// if (jan.Age &lt; jan.DynamicProperties().NumberOfDrinkingBuddies)
    /// Console.WriteLine("Jan drinks too much");
    /// </example>
    /// <remarks>
    /// If you get 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' you should reference Microsoft.CSharp
    /// </remarks>
    public static class ObjectExtensions
    {
        ///<summary>Stores extended data for objects</summary>
        private static ConditionalWeakTable<object, object> extendedData = new ConditionalWeakTable<object, object>();

        /// <summary>
        /// Gets a dynamic collection of properties associated with an object instance,
        /// with a lifetime scoped to the lifetime of the object
        /// </summary>
        /// <param name="obj">The object the properties are associated with</param>
        /// <returns>A dynamic collection of properties associated with an object instance.</returns>
        public static dynamic DynamicProperties(this object obj) => extendedData.GetValue(obj, _ => new ExpandoObject());
    }
}

Um exemplo de uso está nos comentários xml:

var jan = new Person("Jan");

jan.Age = 24; // regular property of the person object;
jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;

if (jan.Age < jan.DynamicProperties().NumberOfDrinkingBuddies)
{
    Console.WriteLine("Jan drinks too much");
}

jan = null; // NumberOfDrinkingBuddies will also be erased during garbage collection

A melhor resposta
N73k

1

Como recentemente precisei disso, procurei a fonte da resposta em:

estender a classe c # adicionando propriedades

e criou uma versão mais dinâmica:

public static class ObjectExtenders
{
    static readonly ConditionalWeakTable<object, List<stringObject>> Flags = new ConditionalWeakTable<object, List<stringObject>>();

    public static string GetFlags(this object objectItem, string key)
    {
        return Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value;
    }

    public static void SetFlags(this object objectItem, string key, string value)
    {
        if (Flags.GetOrCreateValue(objectItem).Any(x => x.Key == key))
        {
            Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value = value;
        }
        else
        {
            Flags.GetOrCreateValue(objectItem).Add(new stringObject()
            {
                Key = key,
                Value = value
            });
        }
    }

    class stringObject
    {
        public string Key;
        public string Value;
    }
}

Provavelmente, pode ser muito melhorado (nomeação, dinâmico em vez de string), atualmente eu o uso no CF 3.5 junto com uma ConditionalWeakTable hacky ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


Desculpe, mas embora isso pareça muito completo, não tem nada a ver com propriedades de extensão, mas apenas mostra métodos de extensão.
Viking
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.