Um guia definitivo para alterações de quebra de API no .NET

227

Gostaria de reunir o máximo de informações possível sobre a versão da API no .NET / CLR e, especificamente, sobre como as alterações na API quebram ou não os aplicativos clientes. Primeiro, vamos definir alguns termos:

Alteração na API - uma alteração na definição publicamente visível de um tipo, incluindo qualquer um de seus membros públicos. Isso inclui alterar o tipo e os nomes dos membros, alterar o tipo base de um tipo, adicionar / remover interfaces da lista de interfaces implementadas de um tipo, adicionar / remover membros (incluindo sobrecargas), alterar a visibilidade do membro, renomear métodos e renomear parâmetros, adicionar valores padrão para parâmetros de método, adicionando / removendo atributos em tipos e membros e adicionando / removendo parâmetros de tipo genérico em tipos e membros (perdi alguma coisa?). Isso não inclui nenhuma alteração nos corpos dos membros ou quaisquer alterações nos membros privados (ou seja, não levamos em consideração a Reflexão).

Quebra no nível binário - uma alteração na API que resulta em assemblies de cliente compilados em relação à versão mais antiga da API, potencialmente não carregando com a nova versão. Exemplo: alterando a assinatura do método, mesmo que permita ser chamado da mesma maneira que antes (ou seja: void para retornar sobrecargas nos valores padrão do tipo / parâmetro).

Quebra no nível da fonte - uma alteração na API que resulta no código existente gravado para compilar em uma versão mais antiga da API, potencialmente não compilando com a nova versão. Entretanto, os assemblies de cliente já compilados funcionam como antes. Exemplo: adicionando uma nova sobrecarga que pode resultar em ambiguidade nas chamadas de método que não eram ambíguas anteriormente.

Mudança de semântica silenciosa no nível de origem - uma mudança de API que resulta em código existente gravado para compilar em uma versão mais antiga da API muda silenciosamente sua semântica, por exemplo, chamando um método diferente. No entanto, o código deve continuar a compilar sem avisos / erros, e os assemblies compilados anteriormente devem funcionar como antes. Exemplo: implementando uma nova interface em uma classe existente que resulta em uma sobrecarga diferente sendo escolhida durante a resolução da sobrecarga.

O objetivo final é catalogar o máximo possível de alterações de API de semântica de interrupção e silêncio e descrever o efeito exato da quebra e quais idiomas são e não são afetados por ela. Para expandir o último: enquanto algumas mudanças afetam todos os idiomas universalmente (por exemplo, adicionar um novo membro a uma interface interrompe as implementações dessa interface em qualquer idioma), algumas requerem semânticas de idiomas muito específicas para entrar em jogo para obter uma pausa. Isso geralmente envolve sobrecarga de método e, em geral, qualquer coisa relacionada a conversões implícitas de tipo. Parece não haver nenhuma maneira de definir o "denominador menos comum" aqui, mesmo para idiomas compatíveis com CLS (ou seja, aqueles que estão em conformidade pelo menos com as regras do "consumidor CLS", conforme definido nas especificações da CLI) - embora eu ' Eu aprecio se alguém me corrigir como errado aqui - então isso terá que ir idioma por idioma. Os de maior interesse são naturalmente os que vêm com o .NET pronto para uso: C #, VB e F #; mas outros, como IronPython, IronRuby, Delphi Prism etc. também são relevantes. Quanto mais difícil for o caso, mais interessante será - coisas como remover membros são bastante evidentes, mas interações sutis entre, por exemplo, sobrecarga de método, parâmetros opcionais / padrão, inferência do tipo lambda e operadores de conversão podem ser muito surpreendentes às vezes.

Alguns exemplos para o kickstart:

Adicionando novas sobrecargas de método

Tipo: quebra no nível da fonte

Idiomas afetados: C #, VB, F #

API antes da alteração:

public class Foo
{
    public void Bar(IEnumerable x);
}

API após alteração:

public class Foo
{
    public void Bar(IEnumerable x);
    public void Bar(ICloneable x);
}

Exemplo de código do cliente trabalhando antes da alteração e quebrado após ela:

new Foo().Bar(new int[0]);

Incluindo novas sobrecargas implícitas do operador de conversão

Tipo: quebra no nível da fonte.

Idiomas afetados: C #, VB

Idiomas não afetados: F #

API antes da alteração:

public class Foo
{
    public static implicit operator int ();
}

API após alteração:

public class Foo
{
    public static implicit operator int ();
    public static implicit operator float ();
}

Exemplo de código do cliente trabalhando antes da alteração e quebrado após ela:

void Bar(int x);
void Bar(float x);
Bar(new Foo());

Notas: F # não está quebrado, porque ele não tem qualquer apoio nível de linguagem para operadores sobrecarregados, nem explícitas nem implícitas - ambos tem que ser chamado diretamente como op_Explicite op_Implicitmétodos.

Adicionando novos métodos de instância

Tipo: a semântica silenciosa no nível da fonte é alterada.

Idiomas afetados: C #, VB

Idiomas não afetados: F #

API antes da alteração:

public class Foo
{
}

API após alteração:

public class Foo
{
    public void Bar();
}

Código de cliente de amostra que sofre uma alteração semântica silenciosa:

public static class FooExtensions
{
    public void Bar(this Foo foo);
}

new Foo().Bar();

Notas: O F # não está quebrado, porque não possui suporte para o nível de idioma ExtensionMethodAttributee requer que os métodos de extensão CLS sejam chamados como métodos estáticos.

Pavel Minaev
fonte
Certamente Microsoft já cobre este ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Robert Harvey
1
@ Robert: seu link é sobre algo muito diferente - descreve mudanças de quebra específicas no próprio .NET Framework . Essa é uma pergunta mais ampla que descreve padrões genéricos que podem introduzir alterações recentes em suas próprias APIs (como autor da biblioteca / estrutura). Não conheço nenhum documento do MS que esteja completo, embora quaisquer links para esse, mesmo que incompletos, sejam definitivamente bem-vindos.
Pavel Minaev 21/09/09
Em qualquer uma dessas categorias de "interrupção", existe alguma em que o problema só se torne aparente em tempo de execução?
Rohit
1
Sim, categoria "quebra binária". Nesse caso, você já tem um assembly de terceiros compilado em todas as versões do seu assembly. Se você soltar uma nova versão do seu assembly no local, o assembly de terceiros para de funcionar - ele simplesmente não carrega no tempo de execução ou funciona incorretamente.
Pavel Minaev 22/09/09
3
Gostaria de acrescentar aqueles no post e comentários blogs.msdn.com/b/ericlippert/archive/2012/01/09/...
Lukasz Madon

Respostas:

42

Alterando uma assinatura de método

Tipo: Ruptura de nível binário

Idiomas afetados: C # (VB e F # provavelmente, mas não testado)

API antes da alteração

public static class Foo
{
    public static void bar(int i);
}

API após alteração

public static class Foo
{
    public static bool bar(int i);
}

Código de cliente de amostra funcionando antes da alteração

Foo.bar(13);
Justin Drury
fonte
15
De fato, também pode ser uma quebra no nível da fonte, se alguém tentar criar um representante para bar.
Pavel Minaev 24/09/09
Isso também é verdade. Encontrei esse problema em particular quando fiz algumas alterações nos utilitários de impressão no aplicativo da minha empresa. Quando a atualização foi lançada, nem todas as DLLs que referenciavam esses utilitários foram recompiladas e lançadas, portanto, lançam uma exceção não encontrada no método.
Justin Drury
1
Isso remonta ao fato de que os tipos de retorno não contam para a assinatura do método. Você também não pode sobrecarregar duas funções baseadas apenas no tipo de retorno. Mesmo problema.
2117 Jason Jason
1
sub-pergunta a esta resposta: alguém sabe a implicação de adicionar um dotnet4 defaultvalue 'public static void bar (int i = 0);' ou alterar esse valor padrão de um valor para outro?
K3b
1
Para aqueles que vão pousar nesta página , penso em C # (e "acho que" na maioria das outras linguagens OOP), os Tipos de retorno não contribuem para a assinatura do método. Sim, a resposta é certa: as alterações de assinatura contribuem para a alteração do nível binário. MAS o exemplo não parece correto IMHO o exemplo correto que eu posso pensar é ANTES da soma decimal pública (int a, int b) Após a soma decimal decimal (decimal a, decimal b) Consulte este link do MSDN 3.6 Assinaturas e sobrecarga
Bhanu Chhabra
40

Adicionando um parâmetro com um valor padrão.

Tipo de quebra: quebra de nível binário

Mesmo que o código-fonte de chamada não precise ser alterado, ele ainda precisará ser recompilado (como ao adicionar um parâmetro regular).

Isso ocorre porque o C # compila os valores padrão dos parâmetros diretamente no assembly de chamada. Isso significa que, se você não recompilar, receberá uma MissingMethodException porque o assembly antigo tenta chamar um método com menos argumentos.

API antes da alteração

public void Foo(int a) { }

API após alteração

public void Foo(int a, string b = null) { }

Código de cliente de amostra que é quebrado posteriormente

Foo(5);

O código do cliente precisa ser recompilado no Foo(5, null)nível do bytecode. A montagem chamada conterá apenas Foo(int, string), não Foo(int). Isso ocorre porque os valores padrão dos parâmetros são puramente um recurso de linguagem, o tempo de execução .Net não sabe nada sobre eles. (Isso também explica por que os valores padrão precisam ser constantes em tempo de compilação em C #).

Eldritch Conundrum
fonte
2
esta é uma alteração de quebra, mesmo para o nível de código fonte: Func<int> f = Foo;// isto irá falhar com a assinatura mudou
Vagaus
26

Este foi muito óbvio quando o descobri, especialmente à luz da diferença com a mesma situação para interfaces. Não é uma pausa, mas é surpreendente o suficiente que eu decidi incluí-la:

Refatorando os Membros da Classe em uma Classe Base

Tipo: não uma pausa!

Idiomas afetados: nenhum (ou seja, nenhum está quebrado)

API antes da alteração:

class Foo
{
    public virtual void Bar() {}
    public virtual void Baz() {}
}

API após alteração:

class FooBase
{
    public virtual void Bar() {}
}

class Foo : FooBase
{
    public virtual void Baz() {}
}

Código de exemplo que continua trabalhando durante toda a alteração (mesmo que eu esperasse que fosse interrompida):

// C++/CLI
ref class Derived : Foo
{
   public virtual void Baz() {{

   // Explicit override    
   public virtual void BarOverride() = Foo::Bar {}
};

Notas:

C ++ / CLI é a única linguagem .NET que possui uma construção análoga à implementação explícita da interface para membros da classe base virtual - "substituição explícita". Eu esperava que isso resultasse no mesmo tipo de quebra que ao mover membros da interface para uma interface base (já que a IL gerada para substituição explícita é a mesma que para implementação explícita). Para minha surpresa, esse não é o caso - embora a IL gerada ainda especifique que BarOverridesubstituições, em Foo::Barvez de FooBase::Bar, o carregador de montagem é inteligente o suficiente para substituir uma pela outra corretamente, sem queixas - aparentemente, o fato de que Fooé uma classe é o que faz a diferença. Vai saber...

Pavel Minaev
fonte
3
Contanto que a classe base esteja na mesma montagem. Caso contrário, é uma alteração de quebra binária.
Jeremy
@ Jeremy, que tipo de código quebra nesse caso? O uso de Baz () de qualquer chamador externo interrompe ou é apenas um problema com pessoas que tentam estender o Foo e substituir o Baz ()?
ChaseMedallion 6/06/06
@ChaseMedallion está quebrando se você for um usuário de segunda mão. Por exemplo, a DLL compilada faz referência a uma versão mais antiga do Foo e você faz referência à DLL compilada, mas também usa uma versão mais recente da DLL do Foo. Rompe com um erro estranho, ou pelo menos aconteceu comigo nas bibliotecas que desenvolvi antes.
Jeremy
19

Este é um caso especial talvez não tão óbvio de "adicionar / remover membros da interface", e achei que ele merece sua própria entrada à luz de outro caso que vou publicar a seguir. Assim:

Refatorando Membros da Interface em uma Interface Base

Tipo: quebras nos níveis de origem e binário

Idiomas afetados: C #, VB, C ++ / CLI, F # (para quebra de origem; o binário afeta naturalmente qualquer idioma)

API antes da alteração:

interface IFoo
{
    void Bar();
    void Baz();
}

API após alteração:

interface IFooBase 
{
    void Bar();
}

interface IFoo : IFooBase
{
    void Baz();
}

Código de cliente de amostra que está quebrado por alterações no nível de origem:

class Foo : IFoo
{
   void IFoo.Bar() { ... }
   void IFoo.Baz() { ... }
}

Código de cliente de amostra que é quebrado por alterações no nível binário;

(new Foo()).Bar();

Notas:

Para uma quebra no nível da fonte, o problema é que C #, VB e C ++ / CLI exigem o nome exato da interface na declaração de implementação dos membros da interface; portanto, se o membro for movido para uma interface base, o código não será mais compilado.

A quebra binária se deve ao fato de os métodos de interface serem totalmente qualificados na IL gerada para implementações explícitas, e o nome da interface também deve ser exato.

A implementação implícita, quando disponível (por exemplo, C # e C ++ / CLI, mas não o VB) funcionará bem no nível de origem e binário. As chamadas de método também não são interrompidas.

Pavel Minaev
fonte
Isso não é verdade para todos os idiomas. Para o VB, não é uma alteração de código-fonte quebrada. Para C # é.
Jeremy
Então, Implements IFoo.Barfará referência transparente IFooBase.Bar?
Pavel Minaev 7/06
Sim, na verdade, você pode fazer referência a um membro direta ou indiretamente através da interface herdada ao implementá-lo. No entanto, essa é sempre uma mudança binária quebrada.
Jeremy
15

Reordenando valores enumerados

Tipo de interrupção: semântica silenciosa no nível de origem / nível binário

Idiomas afetados: todos

Reordenar valores enumerados manterá a compatibilidade no nível da fonte, pois os literais têm o mesmo nome, mas seus índices ordinais serão atualizados, o que pode causar alguns tipos de interrupções silenciosas no nível da fonte.

Pior ainda são as quebras de nível binário silenciosas que podem ser introduzidas se o código do cliente não for recompilado na nova versão da API. Os valores enum são constantes em tempo de compilação e, como tal, qualquer uso deles é inserido na IL do assembly do cliente. Às vezes, esse caso pode ser particularmente difícil de detectar.

API antes da alteração

public enum Foo
{
   Bar,
   Baz
}

API após alteração

public enum Foo
{
   Baz,
   Bar
}

Exemplo de código do cliente que funciona, mas é quebrado posteriormente:

Foo.Bar < Foo.Baz
glopes
fonte
12

Essa é realmente uma coisa muito rara na prática, mas, mesmo assim, surpreendente quando isso acontece.

Adicionando novos membros não sobrecarregados

Tipo: quebra no nível da fonte ou mudança semântica silenciosa.

Idiomas afetados: C #, VB

Idiomas não afetados: F #, C ++ / CLI

API antes da alteração:

public class Foo
{
}

API após alteração:

public class Foo
{
    public void Frob() {}
}

Código de cliente de amostra quebrado por alteração:

class Bar
{
    public void Frob() {}
}

class Program
{
    static void Qux(Action<Foo> a)
    {
    }

    static void Qux(Action<Bar> a)
    {
    }

    static void Main()
    {
        Qux(x => x.Frob());        
    }
}

Notas:

O problema aqui é causado pela inferência do tipo lambda em C # e VB na presença de resolução de sobrecarga. Uma forma limitada de tipagem de pato é empregada aqui para romper laços onde mais de um tipo corresponde, verificando se o corpo do lambda faz sentido para um determinado tipo - se apenas um tipo resultar em corpo compilável, esse será escolhido.

O perigo aqui é que o código do cliente pode ter um grupo de métodos sobrecarregado, em que alguns métodos usam argumentos de seus próprios tipos e outros usam argumentos de tipos expostos por sua biblioteca. Se algum código dele se basear no algoritmo de inferência de tipo para determinar o método correto com base apenas na presença ou ausência de membros, a adição de um novo membro a um de seus tipos com o mesmo nome que em um dos tipos do cliente pode gerar inferência desativado, resultando em ambiguidade durante a resolução de sobrecarga.

Observe que os tipos Fooe Barneste exemplo não estão relacionados de forma alguma, nem por herança nem por outro motivo. O simples uso deles em um único grupo de métodos é suficiente para acionar isso e, se isso ocorrer no código do cliente, você não terá controle sobre ele.

O código de exemplo acima demonstra uma situação mais simples em que essa é uma quebra no nível da fonte (ou seja, resultados de erro do compilador). No entanto, isso também pode ser uma mudança semântica silenciosa, se a sobrecarga escolhida por inferência tiver outros argumentos que, de outra forma, a classificariam abaixo (por exemplo, argumentos opcionais com valores padrão ou incompatibilidade de tipo entre o argumento declarado e o real que requer um implícito) conversão). Nesse cenário, a resolução de sobrecarga não falhará mais, mas uma sobrecarga diferente será silenciosamente selecionada pelo compilador. Na prática, no entanto, é muito difícil encontrar esse caso sem construir cuidadosamente assinaturas de método para causá-lo deliberadamente.

Pavel Minaev
fonte
9

Converta uma implementação implícita da interface em explícita.

Tipo de interrupção: origem e binário

Idiomas afetados: todos

Isso é realmente apenas uma variação da alteração da acessibilidade de um método - é apenas um pouco mais sutil, pois é fácil ignorar o fato de que nem todo acesso aos métodos de uma interface é necessariamente através de uma referência ao tipo da interface.

API antes da alteração:

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator();
}

API após alteração:

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator();
}

Código de cliente de amostra que funciona antes da alteração e é quebrado posteriormente:

new Foo().GetEnumerator(); // fails because GetEnumerator() is no longer public
LBushkin
fonte
7

Converta uma implementação explícita da interface em implícita.

Tipo de intervalo: Fonte

Idiomas afetados: todos

A refatoração de uma implementação explícita da interface em uma implícita é mais sutil na maneira como ela pode quebrar uma API. Na superfície, parece que isso deve ser relativamente seguro; no entanto, quando combinado com herança, pode causar problemas.

API antes da alteração:

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() { yield return "Foo"; }
}

API após alteração:

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator() { yield return "Foo"; }
}

Código de cliente de amostra que funciona antes da alteração e é quebrado posteriormente:

class Bar : Foo, IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() // silently hides base instance
    { yield return "Bar"; }
}

foreach( var x in new Bar() )
    Console.WriteLine(x);    // originally output "Bar", now outputs "Foo"
LBushkin
fonte
Desculpe, eu não sigo muito bem - certamente o código de amostra antes da alteração da API não seria compilado, pois antes da alteração Foonão havia um método público chamado GetEnumerator, e você está chamando o método por meio de uma referência do tipo Foo.. .
Pavel Minaev
Na verdade, tentei simplificar um exemplo da memória e acabou 'foobar' (perdoe o trocadilho). Atualizei o exemplo para demonstrar corretamente o caso (e ser compilável).
LBushkin 30/10/09
No meu exemplo, o problema é causado por mais do que apenas a transição de um método de interface de implícita para pública. Depende da maneira como o compilador C # determina qual método chamar em um loop foreach. Dadas as regras de resolução que o compilador executa, ele alterna da versão na classe derivada para a versão na classe base.
LBushkin 30/10/09
Você esqueceu yield return "Bar":), mas sim, eu vejo aonde isso está indo agora - foreachsempre chama o método público chamado GetEnumerator, mesmo que não seja a implementação real IEnumerable.GetEnumerator. Isso parece ter mais um ângulo: mesmo que você tenha apenas uma classe, e ela implemente IEnumerableexplicitamente, isso significa que é uma mudança de origem para adicionar um método público nomeado GetEnumeratora ela, porque agora foreachusará esse método na implementação da interface. Além disso, o mesmo problema é aplicável à IEnumeratorimplementação ...
Pavel Minaev 30/10/09
6

Alterando um campo para uma propriedade

Tipo de interrupção: API

Idiomas afetados: Visual Basic e C # *

Informações: Quando você altera um campo ou variável normal em uma propriedade no visual basic, qualquer código externo que faça referência a esse membro de qualquer forma precisará ser recompilado.

API antes da alteração:

Public Class Foo    
    Public Shared Bar As String = ""    
End Class

API após alteração:

Public Class Foo
    Private Shared _Bar As String = ""
    Public Shared Property Bar As String
        Get
            Return _Bar
        End Get
        Set(value As String)
            _Bar = value
        End Set
    End Property
End Class    

Exemplo de código do cliente que funciona, mas é quebrado posteriormente:

Foo.Bar = "foobar"
Hagelt18
fonte
2
Ele também quebraria as coisas em C #, porque as propriedades não podem ser usadas oute os refargumentos dos métodos, ao contrário dos campos, e não podem ser o alvo do &operador unário .
Pavel Minaev 22/11/2013
5

Adição de namespace

Quebra no nível da fonte / semântica silenciosa no nível da fonte

Devido à maneira como a resolução do espaço para nome funciona no vb.Net, adicionar um espaço para nome a uma biblioteca pode fazer com que o código do Visual Basic compilado com uma versão anterior da API não compile com uma nova versão.

Código de cliente de amostra:

Imports System
Imports Api.SomeNamespace

Public Class Foo
    Public Sub Bar()
        Dim dr As Data.DataRow
    End Sub
End Class

Se uma nova versão da API adicionar o espaço para nome Api.SomeNamespace.Data, o código acima não será compilado.

Torna-se mais complicado com as importações de namespace no nível do projeto. Se Imports Systemfor omitido do código acima, mas o Systemespaço para nome for importado no nível do projeto, o código ainda poderá resultar em um erro.

No entanto, se a API incluir uma classe DataRowem seu Api.SomeNamespace.Dataespaço para nome, o código será compilado, mas drserá uma instância de System.Data.DataRowquando compilado com a versão antiga da API e Api.SomeNamespace.Data.DataRowquando compilado com a nova versão da API.

Renomeação de Argumento

Interrupção no nível da fonte

Alterar os nomes dos argumentos é uma alteração de quebra no vb.net da versão 7 (?) (.Net versão 1?) E c # .net da versão 4 (.Net versão 4).

API antes da alteração:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

API após alteração:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string y) {
           ...
        }
    }
}

Código de cliente de amostra:

Api.SomeNamespace.Foo.Bar(x:"hi"); //C#
Api.SomeNamespace.Foo.Bar(x:="hi") 'VB

Parâmetros de referência

Interrupção no nível da fonte

A adição de uma substituição de método com a mesma assinatura, exceto que um parâmetro é passado por referência e não por valor, fará com que a origem vb que referencia a API não consiga resolver a função. O Visual Basic não tem como (?) Diferenciar esses métodos no ponto de chamada, a menos que tenham nomes de argumentos diferentes, portanto, essa alteração pode fazer com que ambos os membros sejam inutilizáveis ​​do código vb.

API antes da alteração:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

API após alteração:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
        public static void Bar(ref string x) {
           ...
        }
    }
}

Código de cliente de amostra:

Api.SomeNamespace.Foo.Bar(str)

Alteração de campo para propriedade

Interrupção no nível binário / Interrupção no nível da fonte

Além da quebra no nível binário óbvia, isso pode causar uma quebra no nível da fonte se o membro for passado para um método por referência.

API antes da alteração:

namespace SomeNamespace {
    public class Foo {
        public int Bar;
    }
}

API após alteração:

namespace SomeNamespace {
    public class Foo {
        public int Bar { get; set; }
    }
}

Código de cliente de amostra:

FooBar(ref Api.SomeNamespace.Foo.Bar);
jswolf19
fonte
4

Alteração na API:

  1. Adicionando o atributo [Obsolete] (você meio que mencionou os atributos; no entanto, isso pode ser uma mudança radical ao usar o aviso como erro).

Quebra de nível binário:

  1. Movendo um tipo de uma montagem para outra
  2. Alterando o espaço para nome de um tipo
  3. Adicionando um tipo de classe base de outra montagem.
  4. Adicionando um novo membro (protegido por evento) que usa um tipo de outro assembly (Class2) como uma restrição de argumento de modelo.

    protected void Something<T>() where T : Class2 { }
  5. Alterar uma classe filho (Class3) para derivar de um tipo em outro assembly quando a classe é usada como argumento de modelo para essa classe.

    protected class Class3 : Class2 { }
    protected void Something<T>() where T : Class3 { }

A semântica silenciosa no nível da fonte é alterada:

  1. Adicionando / removendo / alterando substituições de Equals (), GetHashCode () ou ToString ()

(não sei onde eles se encaixam)

Alterações na implantação:

  1. Adicionando / removendo dependências / referências
  2. Atualizando dependências para versões mais recentes
  3. Alterando a 'plataforma de destino' entre x86, Itanium, x64 ou anycpu
  4. Construir / testar em uma instalação de estrutura diferente (por exemplo, instalar 3.5 em uma caixa .Net 2.0 permite chamadas de API que exigem o .Net 2.0 SP2)

Alterações na inicialização / configuração:

  1. Adição / remoção / alteração de opções de configuração personalizadas (por exemplo, configurações do App.config)
  2. Com o uso intenso de IoC / DI nos aplicativos de hoje, é necessário reconfigurar e / ou alterar o código de inicialização para código dependente de DI.

Atualizar:

Desculpe, eu não percebi que a única razão pela qual isso estava quebrando para mim era o fato de usá-los em restrições de modelo.

csharptest.net
fonte
"Adicionando um novo membro (protegido por evento) que usa um tipo de outro assembly." - IIRC, o cliente precisa apenas referenciar os assemblies dependentes que contêm os tipos básicos dos assemblies aos quais ele já faz referência; ele não precisa fazer referência a assemblies que são apenas usados ​​(mesmo que os tipos estejam nas assinaturas de método); Não tenho 100% de certeza sobre isso. Você tem uma referência para regras precisas para isso? Além disso, mover um tipo pode ser ininterrupto se TypeForwardedToAttributefor usado.
Pavel Minaev 08/10/09
Que "TypeForwardedTo" é uma novidade para mim, vou dar uma olhada. Quanto ao outro, também não estou 100% nisso ... deixe-me ver se posso reproduzir novamente e atualizarei a postagem.
Csharptest.net # 8/09
Portanto, não force -Werroro sistema de compilação que você envia com tarballs de lançamento. Esse sinalizador é mais útil para o desenvolvedor do código e geralmente não é útil para o consumidor.
binki
excelente ponto @binki, tratar os avisos como erros deve ser suficiente apenas nas compilações DEBUG.
Csharptest.net
3

Incluindo métodos de sobrecarga para diminuir o uso de parâmetros padrão

Tipo de interrupção: a semântica silenciosa no nível da fonte é alterada

Como o compilador transforma chamadas de método com valores de parâmetros padrão ausentes em uma chamada explícita com o valor padrão no lado da chamada, é dada compatibilidade para o código compilado existente; um método com a assinatura correta será encontrado para todo o código compilado anteriormente.

Por outro lado, as chamadas sem o uso de parâmetros opcionais agora são compiladas como uma chamada para o novo método que está ausente no parâmetro opcional. Tudo ainda está funcionando bem, mas se o código chamado residir em outro assembly, o código compilado recentemente chamando agora será dependente da nova versão deste assembly. A implantação de assemblies que chamam o código refatorado sem também implantar o assembly em que o código refatorado reside está resultando em exceções "método não encontrado".

API antes da alteração

  public int MyMethod(int mandatoryParameter, int optionalParameter = 0)
  {
     return mandatoryParameter + optionalParameter;
  }    

API após alteração

  public int MyMethod(int mandatoryParameter, int optionalParameter)
  {
     return mandatoryParameter + optionalParameter;
  }

  public int MyMethod(int mandatoryParameter)
  {
     return MyMethod(mandatoryParameter, 0);
  }

Código de exemplo que ainda estará funcionando

  public int CodeNotDependentToNewVersion()
  {
     return MyMethod(5, 6); 
  }

Código de exemplo que agora depende da nova versão ao compilar

  public int CodeDependentToNewVersion()
  {
     return MyMethod(5); 
  }
Você não sabe
fonte
1

Renomeando uma interface

Meio que Break: Source e Binary

Idiomas afetados: Provavelmente todos, testados em C #.

API antes da alteração:

public interface IFoo
{
    void Test();
}

public class Bar
{
    IFoo GetFoo() { return new Foo(); }
}

API após alteração:

public interface IFooNew // Of the exact same definition as the (old) IFoo
{
    void Test();
}

public class Bar
{
    IFooNew GetFoo() { return new Foo(); }
}

Exemplo de código do cliente que funciona, mas é quebrado posteriormente:

new Bar().GetFoo().Test(); // Binary only break
IFoo foo = new Bar().GetFoo(); // Source and binary break
Aidiakapi
fonte
1

Método de sobrecarga com um parâmetro do tipo anulável

Tipo: quebra no nível da fonte

Idiomas afetados: C #, VB

API antes de uma alteração:

public class Foo
{
    public void Bar(string param);
}

API após a alteração:

public class Foo
{
    public void Bar(string param);
    public void Bar(int? param);
}

Exemplo de código do cliente trabalhando antes da alteração e quebrado após ela:

new Foo().Bar(null);

Exceção: a chamada é ambígua entre os seguintes métodos ou propriedades.

Bohdan Spilnyi
fonte
0

Promoção para um método de extensão

Tipo: quebra no nível da fonte

Idiomas afetados: C # v6 e superior (talvez outros?)

API antes da alteração:

public static class Foo
{
    public static void Bar(string x);
}

API após alteração:

public static class Foo
{
    public void Bar(this string x);
}

Exemplo de código do cliente trabalhando antes da alteração e quebrado após ela:

using static Foo;

class Program
{
    static void Main() => Bar("hello");
}

Mais informações: https://github.com/dotnet/csharplang/issues/665

rory.ap
fonte