Qual é o caso de canto mais estranho que você já viu em C # ou .NET? [fechadas]


322

Coleciono alguns casos de canto e quebra-cabeças e sempre gostaria de ouvir mais. A página realmente cobre apenas bits e bobs da linguagem C #, mas também acho interessantes as principais coisas do .NET. Por exemplo, aqui está um que não está na página, mas que acho incrível:

string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));

Eu esperaria que imprimisse False - afinal, "new" (com um tipo de referência) sempre cria um novo objeto, não é? As especificações para C # e a CLI indicam que deveria. Bem, não neste caso em particular. Ele imprime True e foi feito em todas as versões do framework com as quais eu testei. (Eu não tentei no Mono, é certo ...)

Só para esclarecer, este é apenas um exemplo do tipo de coisa que estou procurando - não estava particularmente procurando por discussão / explicação sobre essa estranheza. (Não é a mesma coisa que a internação de strings normal; em particular, a internação de strings normalmente não acontece quando um construtor é chamado.) Eu estava realmente pedindo um comportamento estranho semelhante.

Alguma outra gema à espreita por aí?


64
Testado em Mono 2.0 rc; retorna True
Marc Gravell

10
tanto cordas acabar sendo string.Empty e parece que o quadro mantém apenas uma referência a esse
Adrian Zanescu

34
É uma coisa de conservação de memória. Consulte a documentação do MSDN para o método estático string.Intern. O CLR mantém um conjunto de cadeias de caracteres. É por isso que strings com conteúdo idêntico aparecem como referências à mesma memória, ou seja, objeto.
31420 John Leidegren

12
@ John: A internação de strings só acontece automaticamente para literais . Esse não é o caso aqui. @DanielSwe: A internação não é necessária para tornar as cordas imutáveis. O fato de que isso é possível é um bom corolário da imutabilidade, mas a internação normal não está acontecendo aqui de qualquer maneira.
23909 Jon Skeet

3
Os detalhes de implementação que causam esse comportamento são explicados aqui: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Respostas:


394

Acho que lhe mostrei isso antes, mas eu gosto da diversão aqui - isso levou uma certa depuração para rastrear! (o código original era obviamente mais complexo e sutil ...)

    static void Foo<T>() where T : new()
    {
        T t = new T();
        Console.WriteLine(t.ToString()); // works fine
        Console.WriteLine(t.GetHashCode()); // works fine
        Console.WriteLine(t.Equals(t)); // works fine

        // so it looks like an object and smells like an object...

        // but this throws a NullReferenceException...
        Console.WriteLine(t.GetType());
    }

Então, o que foi T ...

Resposta: qualquer Nullable<T>- como int?. Todos os métodos são substituídos, exceto GetType () que não pode ser; portanto, é convertido (em caixa) para objeto (e, portanto, para nulo) para chamar object.GetType () ... que chama nulo ;-p


Atualização: o enredo engrossa ... Ayende Rahien lançou um desafio semelhante em seu blog , mas com um where T : class, new():

private static void Main() {
    CanThisHappen<MyFunnyType>();
}

public static void CanThisHappen<T>() where T : class, new() {
    var instance = new T(); // new() on a ref-type; should be non-null, then
    Debug.Assert(instance != null, "How did we break the CLR?");
}

Mas pode ser derrotado! Usando o mesmo indireto usado por coisas como comunicação remota; aviso - o seguinte é puro mal :

class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }

Com isso, a new()chamada é redirecionada para o proxy ( MyFunnyProxyAttribute), que retorna null. Agora vá e lave os olhos!


9
Por que não é possível definir Nullable <T> .GetType ()? O resultado não deve ser typeof (Nullable <T>)?
Drew Noakes

69
Drew: o problema é que GetType () não é virtual, portanto não é substituído - o que significa que o valor está na caixa para a chamada do método. A caixa se torna uma referência nula, daí o NRE.
Jon Skeet

10
@Desenhou; Além disso, existem regras de boxe especiais para Nullable <T>, o que significa que um Nullable <T> vazio é nulo, não uma caixa que contém um Nullable <T> vazio (e um nulo desmarca um Nullable <T vazio >)
Marc Gravell

29
Muito, muito legal. De uma maneira não legal. ;-)
Konrad Rudolph

6
Construtor-constrangimento, 10.1.5 no C # 3.0 especificação langauge
Marc Gravell

216

Arredondamento de Banqueiros.

Este não é tanto um bug ou mau funcionamento do compilador, mas certamente um caso estranho ...

O .Net Framework emprega um esquema ou arredondamento conhecido como Arredondamento do Banqueiro.

No arredondamento dos banqueiros, os números 0,5 são arredondados para o número par mais próximo,

Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...

Isso pode levar a alguns erros inesperados nos cálculos financeiros com base no arredondamento mais conhecido da metade para cima.

Isso também se aplica ao Visual Basic.


22
Pareceu-me estranho também. Isso é, pelo menos, até eu ter arredondado uma grande lista de números e calcular sua soma. Você então percebe que, se simplesmente arredondar para cima, terá uma diferença potencialmente enorme da soma dos números não-arredondados. Muito ruim se você estiver fazendo cálculos financeiros!
Tsvetomir Tsonev

255
Caso as pessoas não soubessem, você pode fazer: Math.Round (x, MidpointRounding.AwayFromZero); Para alterar o esquema de arredondamento.
ICR

26
Dos documentos: O comportamento deste método segue a Norma IEEE 754, seção 4. Esse tipo de arredondamento às vezes é chamado de arredondamento para o mais próximo, ou arredondamento de banqueiro. Minimiza os erros de arredondamento resultantes do arredondamento consistente de um valor do ponto médio em uma única direção.
ICR

8
Gostaria de saber se é por isso que vejo int(fVal + 0.5)tantas vezes, mesmo em idiomas que possuem uma função de arredondamento embutida.
2145 Ben Blank

32
Ironicamente, eu trabalhei em um banco uma vez e os outros programadores começaram a falar sobre isso, pensando que o arredondamento estava quebrado na estrutura
dan

176

O que essa função fará se for chamada como Rec(0)(não no depurador)?

static void Rec(int i)
{
    Console.WriteLine(i);
    if (i < int.MaxValue)
    {
        Rec(i + 1);
    }
}

Responda:

  • No JIT de 32 bits, deve resultar em um StackOverflowException
  • No JIT de 64 bits, ele deve imprimir todos os números para int.MaxValue

Isso ocorre porque o compilador JIT de 64 bits aplica otimização de chamada de cauda , enquanto o JIT de 32 bits não.

Infelizmente, não tenho uma máquina de 64 bits disponível para verificar isso, mas o método atende a todas as condições para otimização da chamada final. Se alguém tiver um, eu estaria interessado em ver se é verdade.


10
Tem de ser compilado no modo de versão, mas a maioria definitivamente funciona em x64 =)
Neil Williams

3
pode valer a pena atualizar seu resposta quando VS 2010 sai uma vez que todas as EIC atuais, então, fazer o TCO no Release modo
ShuggyCoUk

3
Tentei apenas o VS2010 Beta 1 no WinXP de 32 bits. Ainda receba uma StackOverflowException.
squillman

130
+1 para o StackOverflowException
calvinlough

7
Isso ++me excitou totalmente. Você não pode ligar Rec(i + 1)como uma pessoa normal?
Configurador

111

Atribuir isto!


É uma pergunta que eu gosto de fazer nas festas (e provavelmente é por isso que não sou mais convidada):

Você pode compilar o seguinte trecho de código?

    public void Foo()
    {
        this = new Teaser();
    }

Uma fraude fácil pode ser:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

Mas a solução real é esta:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

Portanto, é pouco sabido que os tipos de valor (estruturas) podem reatribuir sua thisvariável.


3
Classes C ++ pode fazer isso também ... como eu descobri um pouco recentemente, apenas para ser gritou para realmente tentando usá-lo para uma otimização: p
MPEN

1
Na verdade, eu estava usando o novo in-place. Só queria uma maneira eficiente para atualizar todos os campos :)
MPEN

70
Esta é também uma fraude: //this = new Teaser();:-)
AndrewJacksonZA

17
:-) Eu prefiro aquelas fraudes no meu código de produção, que tal abominação redesignação ...
Omer Mor

2
Do CLR via C #: A razão pela qual eles fizeram isso é porque você pode chamar o construtor sem parâmetros de uma estrutura em outro construtor. Se você deseja inicializar apenas um valor de uma estrutura e deseja que os outros valores sejam zero / nulos (padrão), você pode escrever public Foo(int bar){this = new Foo(); specialVar = bar;}. Isso não é eficiente e não é realmente justificado ( specialVaré atribuído duas vezes), mas apenas para sua informação. (Essa é a razão dada no livro, eu não sei por que não devemos apenas fazer public Foo(int bar) : this())
kizzx2

100

Há alguns anos, quando trabalhamos no programa de lealdade, tivemos um problema com a quantidade de pontos atribuídos aos clientes. O problema estava relacionado à conversão / conversão dupla para int.

No código abaixo:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;

i1 == i2 ?

Acontece que i1! = I2. Devido às diferentes políticas de arredondamento no operador Converter e converter, os valores reais são:

i1 == 14
i2 == 13

É sempre melhor chamar Math.Ceiling () ou Math.Floor () (ou Math.Round com MidpointRounding que atenda aos nossos requisitos)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);

44
A conversão para um número inteiro não arredonda, apenas a corta (efetivamente sempre arredondando para baixo). Então, isso faz todo sentido.
Max Schmeling

57
@ Max: sim, mas por que converter rodada?
237 Stefan Steinegger #

18
@Stefan Steinegger Se tudo o que fiz foi lançado, não haveria razão para isso em primeiro lugar, não é? Observe também que o nome da classe é Convert not Cast.
1 de

3
Em VB: rodadas CInt (). Fix () trunca. Me queimou uma vez ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren

74

Eles deveriam ter transformado 0 em um número inteiro, mesmo quando há uma sobrecarga na função enum.

Eu conhecia a lógica da equipe principal do C # para mapear 0 para enum, mas ainda assim, não é tão ortogonal quanto deveria. Exemplo do Npgsql .

Exemplo de teste:

namespace Craft
{
    enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };


   class Mate
    {
        static void Main(string[] args)
        {

            JustTest(Symbol.Alpha); // enum
            JustTest(0); // why enum
            JustTest((int)0); // why still enum

            int i = 0;

            JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version

            JustTest(i); // it's ok from down here and below
            JustTest(1);
            JustTest("string");
            JustTest(Guid.NewGuid());
            JustTest(new DataTable());

            Console.ReadLine();
        }

        static void JustTest(Symbol a)
        {
            Console.WriteLine("Enum");
        }

        static void JustTest(object o)
        {
            Console.WriteLine("Object");
        }
    }
}

18
Uau, isso é novo para mim. Também estranho como ConverTo.ToIn32 () funciona, mas a conversão para (int) 0 não. E qualquer outro número> 0 funciona. (Por "obras" Quero dizer chamar a sobrecarga de objeto.)
Lucas

Há uma regra de análise de código recomendada para impor boas práticas nesse comportamento: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Esse link também contém uma boa descrição de como o mapeamento 0 funciona .
22420 Chris Clark

1
@ Chris Clark: eu tentei colocar None = 0 no símbolo enum. ainda o compilador escolhe enum para 0 e par (int) 0
Michael Buen

2
Na IMO, eles deveriam ter introduzido uma palavra-chave noneque pode ser usada convertida em qualquer enum e tornado 0 sempre um int e não implicitamente convertível em um enum.
CodesInChaos

5
ConverTo.ToIn32 () funciona porque seu resultado não é constante de compilação. E apenas a constante de compilação 0 é conversível em um enum. Nas versões anteriores do .net, mesmo apenas o literal 0deveria ter sido convertido em enum. Veja o blog de Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

Este é um dos mais incomuns que eu já vi até agora (além dos aqui, é claro!):

public class Turtle<T> where T : Turtle<T>
{
}

Ele permite que você a declare, mas não tem uso real, pois sempre solicitará que você envolva qualquer classe que você colocar no centro com outra Tartaruga.

[piada] Eu acho que são tartarugas até o fim ... [/ piada]


34
Porém, você pode criar instâncias:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
De fato. Esse é o padrão usado pelas enums do Java com grande efeito. Eu também o uso em Buffers de Protocolo.
Jon Skeet

6
RCIX, sim é.
Joshua Joshua

8
Eu usei esse padrão bastante em coisas genéricas sofisticadas. Ele permite coisas como um clone digitado corretamente ou a criação de instâncias dele mesmo.
Lucero

20
Este é o 'padrão de modelo curiosamente recorrente' en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

Aqui está um que eu descobri recentemente ...

interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

A descrição acima parece louca à primeira vista, mas é realmente legal. Não, realmente (embora eu tenha perdido uma parte importante, mas não seja algo hacky como "adicione uma classe chamada IFoo" ou "adicione um usingalias para apontar IFoopara um classe").

Veja se você consegue descobrir o porquê: Quem disse que você não pode instanciar uma interface?


1
+1 para "usar alias" - eu nunca soube que você poderia fazer isso !
David

hack no compilador para COM Interop :-)
Ion Todirel 3/11/11

Seu desgraçado! Você poderia pelo menos ter dito "sob certas circunstâncias" ... Meu compilador refuta!
MA Hanin

56

Quando um booleano não é verdadeiro nem falso?

Bill descobriu que você pode hackear um booleano para que, se A for Verdadeiro e B for Verdadeiro, (A e B) for Falso.

Booleanos hackeados


134
Quando é FILE_NOT_FOUND, é claro!
Greg

12
Isso é interessante porque significa, matematicamente falando, que nenhuma declaração em C # é comprovável. Opa.
Simon Johnson

20
Um dia vou escrever um programa que depende desse comportamento, e os demônios do inferno mais sombrio prepararão uma recepção para mim. Bwahahahahaha!
Jeffrey L Whitledge

18
Este exemplo usa operadores bit a bit, não lógicos. Como isso é surpreendente?
21139 Josh Lee

6
Bem, ele corta o layout da estrutura, é claro que você obterá resultados estranhos, isso não é tão surpreendente ou inesperado!
Ion Todirel

47

Estou chegando um pouco atrasado para a festa, mas tenho três quatro cinco:

  1. Se você pesquisar InvokeRequired em um controle que não tenha sido carregado / exibido, ele será falso - e explodirá na sua cara se você tentar alterá-lo de outro thread ( a solução é fazer referência a isso. ao controle).

  2. Outro que me fez tropeçar é que, dada uma assembléia com:

    enum MyEnum
    {
        Red,
        Blue,
    }

    se você calcular MyEnum.Red.ToString () em outro assembly e, no intervalo de tempo, alguém recompilar sua enumeração para:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    em tempo de execução, você receberá "Black".

  3. Eu tinha uma assembléia compartilhada com algumas constantes úteis. Meu antecessor havia deixado um monte de propriedades de get-only de aparência feia, pensei em me livrar da bagunça e usar apenas a const pública. Fiquei um pouco surpreso quando o VS os compilou com seus valores, e não com referências.

  4. Se você implementar um novo método de uma interface a partir de outro assembly, mas reconstruir a referência à versão antiga desse assembly, obterá uma TypeLoadException (sem implementação de 'NewMethod'), mesmo que você o tenha implementado (consulte aqui ).

  5. Dicionário <,>: "A ordem na qual os itens são retornados é indefinida". Isso é horrível , porque pode morder você às vezes, mas trabalhar outros, e se você apenas assumiu cegamente que o Dictionary vai se comportar bem ("por que não deveria? Eu pensei, a lista"), você realmente precisa coloque seu nariz nele antes de finalmente começar a questionar sua suposição.


6
# 2 é um exemplo interessante. Enums são mapeamentos de compilador para valores integrais. Portanto, mesmo que você não tenha explicitamente atribuído valores a eles, o compilador o fez, resultando em MyEnum.Red = 0 e MyEnum.Blue = 1. Quando você adicionou Black, redefiniu o valor 0 para mapear de Vermelho para Preto. Eu suspeito que o problema também teria se manifestado em outros usos, como serialização.
LBushkin

3
+1 para Invocar necessário. No nosso caso, preferimos atribuir explicitamente valores a enumerações como Vermelho = 1, Azul = 2, para que um novo possa ser inserido antes ou depois, sempre resultando no mesmo valor. É especialmente necessário se você estiver salvando valores nos bancos de dados.
TheVillageIdiot

53
Eu discordo que o nº 5 é um "caso extremo". O dicionário não deve ter uma ordem definida com base em quando você insere valores. Se você deseja uma ordem definida, use uma Lista ou uma chave que possa ser classificada de uma maneira útil para você ou use uma estrutura de dados totalmente diferente.
06 de

21
@ Cunha, como SortedDictionary, talvez?
Allon Guralnek

4
O nº 3 acontece porque as constantes são inseridas como literais em todos os lugares em que são usadas (em C #, pelo menos). Seu antecessor já deve ter percebido isso, e é por isso que eles usaram a propriedade get-only. No entanto, uma variável readonly (em oposição a uma const) também funcionaria.
Remoun 30/09/09

33

VB.NET, anuláveis ​​e o operador ternário:

Dim i As Integer? = If(True, Nothing, 5)

Isso levou algum tempo para depurar, pois esperava iconter Nothing.

O que eu realmente contém? 0.

Isso é surpreendente, mas, na verdade, o comportamento "correto": Nothingno VB.NET não é exatamente o mesmo que nullno CLR: Nothingpode significar nullou default(T)para um tipo de valor T, dependendo do contexto. No caso acima, Ifdeduz Integercomo o tipo comum de Nothinge 5, portanto, neste caso, Nothingsignifica 0.


Interessante o suficiente, não consegui encontrar esta resposta, então tive que criar uma pergunta . Bem, quem sabia que a resposta está nesta discussão?
GSerg

28

Encontrei uma segunda caixa de esquina realmente estranha que supera a minha primeira por um tiro no escuro.

O método String.Equals (String, String, StringComparison) não é realmente livre de efeitos colaterais.

Eu estava trabalhando em um bloco de código que tinha isso em uma linha por si só na parte superior de alguma função:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

A remoção dessa linha leva a um estouro de pilha em outro lugar do programa.

O código acabou instalando um manipulador para o que era essencialmente um evento BeforeAssemblyLoad e tentando fazer

if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
    assemblyfilename = "someparticular_modified.dll";
}

Até agora eu não deveria ter que lhe dizer. O uso de uma cultura que não foi usada antes em uma comparação de cadeias causa uma carga de montagem. InvariantCulture não é uma exceção a isso.


Eu acho que "carregar uma montagem" é um efeito colateral, pois você pode observá-lo com o BeforeAssemblyLoad!
26630 Jacob Krall

2
Uau. Este é um tiro perfeito na perna do mantenedor. Eu acho que escrever um manipulador BeforeAssemblyLoad pode levar a muitas dessas surpresas.
Wigy

20

Aqui está um exemplo de como você pode criar uma estrutura que causa a mensagem de erro "Tentativa de ler ou gravar memória protegida. Isso geralmente é uma indicação de que outra memória está corrompida". A diferença entre sucesso e fracasso é muito sutil.

O teste de unidade a seguir demonstra o problema.

Veja se você consegue descobrir o que deu errado.

    [Test]
    public void Test()
    {
        var bar = new MyClass
        {
            Foo = 500
        };
        bar.Foo += 500;

        Assert.That(bar.Foo.Value.Amount, Is.EqualTo(1000));
    }

    private class MyClass
    {
        public MyStruct? Foo { get; set; }
    }

    private struct MyStruct
    {
        public decimal Amount { get; private set; }

        public MyStruct(decimal amount) : this()
        {
            Amount = amount;
        }

        public static MyStruct operator +(MyStruct x, MyStruct y)
        {
            return new MyStruct(x.Amount + y.Amount);
        }

        public static MyStruct operator +(MyStruct x, decimal y)
        {
            return new MyStruct(x.Amount + y);
        }

        public static implicit operator MyStruct(int value)
        {
            return new MyStruct(value);
        }

        public static implicit operator MyStruct(decimal value)
        {
            return new MyStruct(value);
        }
    }

Minha cabeça dói ... Por que isso não funciona?
jasonh

2
Hum, eu escrevi isso alguns meses atrás, mas não me lembro por que exatamente isso aconteceu.
CBP

10
Parece um bug do compilador; as += 500chamadas: ldc.i4 500(empurra 500 como um Int32), então call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- então ele trata como um decimal(96 bits) sem nenhuma conversão. Se você usar, += 500Mele acerta. Simplesmente parece que o compilador pensa que pode fazê-lo de uma maneira (presumivelmente devido ao operador int implícito) e depois decide fazê-lo de outra maneira.
Marc Gravell

1
Desculpe pelo post duplo, aqui está uma explicação mais qualificada. Vou acrescentar isso, eu fui pouco com isso e isso é péssimo, mesmo que eu entenda por que isso acontece. Para mim, essa é uma limitação infeliz do tipo struct / valuet. bytes.com/topic/net/answers/…
Bennett Dill

2
@ Bem, é possível obter erros do compilador ou a modificação que não afeta a estrutura original. Uma violação de acesso é uma fera completamente diferente. O tempo de execução nunca deve ser lançado se você estiver apenas escrevendo código gerenciado puro e seguro.
CodesInChaos

18

O C # suporta conversões entre matrizes e listas, desde que as matrizes não sejam multidimensionais e haja uma relação de herança entre os tipos e os tipos são tipos de referência

object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;

// Also works for IList (and IEnumerable, ICollection)
IList<string> sList = (IList<string>)oArray;
IList<object> oList = new string[] { "one", "two", "three" };

Observe que isso não funciona:

object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]'
int[] iArray = (int[])oArray2;            // Error: Cannot convert type 'object[]' to 'int[]'

11
O exemplo IList <T> é apenas uma conversão, porque a string [] já implementa ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> e IEnumerable <string>.
Lucas

15

Este é o mais estranho que encontrei por acidente:

public class DummyObject
{
    public override string ToString()
    {
        return null;
    }
}

Utilizado da seguinte forma:

DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);

Jogará um NullReferenceException. Acontece que as várias adições são compiladas pelo compilador C # em uma chamada para String.Concat(object[]). Antes do .NET 4, havia um bug exatamente nessa sobrecarga do Concat, onde o objeto era verificado como nulo, mas não o resultado de ToString ():

object obj2 = args[i];
string text = (obj2 != null) ? obj2.ToString() : string.Empty;
// if obj2 is non-null, but obj2.ToString() returns null, then text==null
int length = text.Length;

Este é um erro do ECMA-334 §14.7.4:

O operador binário + executa concatenação de cadeia de caracteres quando um ou ambos os operandos são do tipo string. Se for um operando de concatenação de cadeias null, uma cadeia vazia será substituída. Caso contrário, qualquer operando que não seja de sequência será convertido em sua representação de sequência, invocando o ToStringmétodo virtual herdado do tipo object. Se ToStringretornar null, uma sequência vazia será substituída.


3
Hmm, mas posso imaginar essa falha como .ToStringrealmente nunca deve retornar nulo, mas string.Empty. No entanto, e erro no quadro.
Dykam 01/03/10

12

Interessante - quando olhei pela primeira vez, presumi que era algo que o compilador C # estava procurando, mas mesmo se você emitir a IL diretamente para remover qualquer chance de interferência, isso ainda acontece, o que significa que realmente é o newobjcódigo operacional que está fazendo o verificação.

var method = new DynamicMethod("Test", null, null);
var il = method.GetILGenerator();

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals"));
il.Emit(OpCodes.Box, typeof(bool));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }));

il.Emit(OpCodes.Ret);

method.Invoke(null, null);

Igualmente equivale a truese você checar contra o string.Emptyque significa que esse código operacional deve ter um comportamento especial para estender cadeias vazias.


para não ser um espertinho ou algo assim, mas você já ouviu falar do refletor ? é bastante útil nesses tipos de casos;
#

3
Você não está sendo inteligente; você está perdendo o objetivo - eu queria gerar uma IL específica para este caso. E de qualquer maneira, considerando que Reflection.Emit é trivial para esse tipo de cenário, é provavelmente tão rápido quanto escrever um programa em C # e abrir o refletor, encontrar o binário, encontrar o método, etc ... E eu nem preciso deixe o IDE para fazer isso.
Greg Beech

10
Public Class Item
   Public ID As Guid
   Public Text As String

   Public Sub New(ByVal id As Guid, ByVal name As String)
      Me.ID = id
      Me.Text = name
   End Sub
End Class

Public Sub Load(sender As Object, e As EventArgs) Handles Me.Load
   Dim box As New ComboBox
   Me.Controls.Add(box)          'Sorry I forgot this line the first time.'
   Dim h As IntPtr = box.Handle  'Im not sure you need this but you might.'
   Try
      box.Items.Add(New Item(Guid.Empty, Nothing))
   Catch ex As Exception
      MsgBox(ex.ToString())
   End Try
End Sub

A saída é "Tentativa de ler a memória protegida. Isso é uma indicação de que outra memória está corrompida".


1
Interessante! Parece um bug do compilador; Eu portado para C # e funciona bem. Dito isto, há muitos problemas com exceções lançadas no Load, e ele se comporta de maneira diferente com / sem um depurador - você pode capturar com um depurador, mas não sem (em alguns casos).
Marc Gravell

Desculpe, esqueci, você precisa adicionar a caixa de combinação ao formulário antes que ela o faça.
Joshua

Isso tem a ver com a inicialização de diálogo usando um SEH como algum tipo de mecanismo de comunicação interna horrível? Lembro-me vagamente de algo assim no Win32.
21119 Daniel Earwicker

1
Este é o mesmo problema cbp acima. O valuetype sendo retornado é uma cópia, portanto, quaisquer referências a quaisquer propriedades decorrentes da referida cópia é dirigido à terra pouco de balde ... bytes.com/topic/net/answers/...
Bennett Dill

1
Não. Não há estruturas aqui. Na verdade, eu depurei. Ele adiciona um NULL à coleção de itens da lista da caixa de combinação nativa, causando uma falha tardia.
Joshua

10

PropertyInfo.SetValue () pode atribuir entradas para enumerações, entradas para entradas anuláveis, enumerações para enumerações anuláveis, mas não entradas para enumerações anuláveis.

enumProperty.SetValue(obj, 1, null); //works
nullableIntProperty.SetValue(obj, 1, null); //works
nullableEnumProperty.SetValue(obj, MyEnum.Foo, null); //works
nullableEnumProperty.SetValue(obj, 1, null); // throws an exception !!!

Descrição completa aqui


10

E se você tiver uma classe genérica que possua métodos que possam se tornar ambíguos, dependendo dos argumentos do tipo? Eu me deparei com essa situação recentemente escrevendo um dicionário bidirecional. Eu queria escrever Get()métodos simétricos que retornassem o oposto de qualquer argumento que fosse passado. Algo assim:

class TwoWayRelationship<T1, T2>
{
    public T2 Get(T1 key) { /* ... */ }
    public T1 Get(T2 key) { /* ... */ }
}

Tudo ficará bem se você criar uma instância em que T1e T2sejam tipos diferentes:

var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");

Mas se T1e T2são os mesmos (e provavelmente se um era uma subclasse de outro), é um erro do compilador:

var r2 = new TwoWayRelationship<int, int>();
r2.Get(1);  // "The call is ambiguous..."

Curiosamente, todos os outros métodos no segundo caso ainda são utilizáveis; são apenas chamadas para o método agora ambíguo que causa um erro do compilador. Caso interessante, se um pouco improvável e obscuro.


Opositores da sobrecarga de método vão adorar esse ^^.
Christian Klauser

1
Eu não sei, isso faz total sentido para mim.
211111 Scott Whitlock

10

Quebra-cabeças de Acessibilidade em C #


A seguinte classe derivada está acessando um campo privado de sua classe base e o compilador silenciosamente olha para o outro lado:

public class Derived : Base
{
    public int BrokenAccess()
    {
        return base.m_basePrivateField;
    }
}

O campo é realmente privado:

private int m_basePrivateField = 0;

Gostaria de adivinhar como podemos compilar esse código?

.

.

.

.

.

.

.

Responda


O truque é declarar Derivedcomo uma classe interna de Base:

public class Base
{
    private int m_basePrivateField = 0;

    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }
}

As classes internas têm acesso total aos membros da classe externa. Nesse caso, a classe interna também deriva da classe externa. Isso nos permite "quebrar" o encapsulamento de membros privados.


Isso na verdade está bem documentado; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Às vezes, pode ser um recurso útil, principalmente se a classe externa for estática.

Sim - claro que está documentado. No entanto, pouquíssimas pessoas resolveram esse quebra-cabeça, então eu pensei que era uma peça interessante de curiosidades.
Omer Mor

2
Parece que você tem uma possibilidade muito forte de um estouro de pilha por ter uma classe interna herda seu dono ...
Jamie Treworgy

Outro caso semelhante (e perfeitamente correto) é que um objeto pode acessar um membro particular de outro objeto do mesmo tipo:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes 12/11/11

10

Só encontrei uma coisinha legal hoje:

public class Base
{
   public virtual void Initialize(dynamic stuff) { 
   //...
   }
}
public class Derived:Base
{
   public override void Initialize(dynamic stuff) {
   base.Initialize(stuff);
   //...
   }
}

Isso gera erro de compilação.

A chamada para o método 'Initialize' precisa ser despachada dinamicamente, mas não pode ser porque faz parte de uma expressão de acesso base. Considere lançar os argumentos dinâmicos ou eliminar o acesso à base.

Se eu escrever base.Initialize (coisas como objeto); funciona perfeitamente, no entanto, isso parece ser uma "palavra mágica" aqui, pois faz exatamente o mesmo, tudo ainda é recebido como dinâmico ...


8

Em uma API que estamos usando, os métodos que retornam um objeto de domínio podem retornar um "objeto nulo" especial. Na implementação disso, o operador de comparação e o Equals()método são substituídos para retornar truese forem comparados com null.

Portanto, um usuário dessa API pode ter um código como este:

return test != null ? test : GetDefault();

ou talvez um pouco mais detalhado, assim:

if (test == null)
    return GetDefault();
return test;

onde GetDefault()é um método que retorna algum valor padrão que queremos usar em vez de null. A surpresa me ocorreu quando eu estava usando o ReSharper e seguindo a recomendação de reescrever isso para o seguinte:

return test ?? GetDefault();

Se o objeto de teste for um objeto nulo retornado da API em vez de um apropriado null, o comportamento do código agora será alterado, pois o operador coalescente nulo realmente verifica null, não está executando operator=ou Equals().


1
não é realmente um caso de esquina, mas querido senhor que pensou nisso?!?
Ray Booysen

Esse código não está apenas usando tipos anuláveis? Portanto, o ReSharper recomendando o "??" usar. Como Ray disse, eu não consideraria isso um caso de esquina; ou eu estou errado?
Tony

1
Sim, os tipos são anuláveis ​​- e há um NullObject além disso. Se é um caso de canto, eu não sei, mas pelo menos é um caso em que 'if (a! = Null) retorna a; return b; ' não é o mesmo que 'retornar a ?? b '. Concordo absolutamente que é um problema com o design da estrutura / API - sobrecarregar == null para retornar true em um objeto certamente não é uma boa ideia!
precisa

8

Considere este caso estranho:

public interface MyInterface {
  void Method();
}
public class Base {
  public void Method() { }
}
public class Derived : Base, MyInterface { }

Se Basee Derivedfor declarado no mesmo assembly, o compilador tornará Base::Methodvirtual e selado (no CIL), mesmo que Basenão implemente a interface.

Se Basee Derivedestiver em assemblies diferentes, ao compilar o Derivedassembly, o compilador não alterará o outro assembly, portanto, introduzirá um membro Derivedque será uma implementação explícita, para a MyInterface::Methodqual apenas delegará a chamada Base::Method.

O compilador precisa fazer isso para suportar o envio polimórfico com relação à interface, ou seja, precisa tornar esse método virtual.


Isso realmente soa estranho. Terá que investigar mais tarde :)
Jon Skeet

@ Jon Skeet: Encontrei isso enquanto pesquisava estratégias de implementação para funções em C # . Seria ótimo receber seus comentários sobre esse!
Jordão

7

O seguinte pode ser de conhecimento geral que simplesmente estava faltando, mas eh. Há algum tempo, tivemos um caso de erro que incluía propriedades virtuais. Abstraindo um pouco o contexto, considere o código a seguir e aplique o ponto de interrupção na área especificada:

class Program
{
    static void Main(string[] args)
    {
        Derived d = new Derived();
        d.Property = "AWESOME";
    }
}

class Base
{
    string _baseProp;
    public virtual string Property 
    { 
        get 
        {
            return "BASE_" + _baseProp;
        }
        set
        {
            _baseProp = value;
            //do work with the base property which might 
            //not be exposed to derived types
            //here
            Console.Out.WriteLine("_baseProp is BASE_" + value.ToString());
        }
    }
}

class Derived : Base
{
    string _prop;
    public override string Property 
    {
        get { return _prop; }
        set 
        { 
            _prop = value; 
            base.Property = value;
        } //<- put a breakpoint here then mouse over BaseProperty, 
          //   and then mouse over the base.Property call inside it.
    }

    public string BaseProperty { get { return base.Property; } private set { } }
}

Enquanto estiver no Derivedcontexto do objeto, você poderá obter o mesmo comportamento ao adicionar base.Propertycomo um relógio ou digitar base.Propertyno quickwatch.

Levei algum tempo para perceber o que estava acontecendo. No final, fui esclarecido pelo Quickwatch. Ao acessar o Quickwatch e explorar o Derivedobjeto d (ou a partir do contexto do objeto this) e selecionar o campo base, o campo de edição na parte superior do Quickwatch exibe o seguinte elenco:

((TestProject1.Base)(d))

O que significa que, se a base for substituída como tal, a chamada será

public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }

para o Watches, Quickwatch e as dicas de ferramentas de depuração do mouse, e faria sentido exibir isso em "AWESOME"vez de "BASE_AWESOME"considerar o polimorfismo. Ainda não sei por que isso a transformaria em elenco, uma hipótese é que calltalvez não esteja disponível no contexto desses módulos e apenas callvirt.

De qualquer forma, isso obviamente não altera nada em termos de funcionalidade, Derived.BasePropertyainda retornará realmente "BASE_AWESOME", e, portanto, essa não foi a raiz do nosso bug no trabalho, apenas um componente confuso. No entanto, achei interessante como isso poderia enganar os desenvolvedores que desconhecem esse fato durante as sessões de depuração, especialmente se Basenão estiverem expostos no seu projeto, mas referenciados como uma DLL de terceiros, resultando em Devs dizendo:

"Oi, espere .. o que? Omg que DLL é como, .. fazendo algo engraçado"


Isso não é nada de especial, é assim que as substituições funcionam.
Configurador

7

Esse é bem difícil de superar. Eu o encontrei enquanto tentava criar uma implementação RealProxy que realmente suporta Begin / EndInvoke (obrigado pela MS por tornar isso impossível de fazer sem hackers horríveis). Este exemplo é basicamente um bug no CLR, o caminho do código não gerenciado para BeginInvoke não valida que a mensagem de retorno de RealProxy.PrivateInvoke (e minha substituição de Invoke) esteja retornando uma instância de um IAsyncResult. Uma vez retornado, o CLR fica incrivelmente confuso e perde qualquer ideia do que está acontecendo, como demonstrado pelos testes na parte inferior.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Reflection;
using System.Runtime.Remoting.Messaging;

namespace BrokenProxy
{
    class NotAnIAsyncResult
    {
        public string SomeProperty { get; set; }
    }

    class BrokenProxy : RealProxy
    {
        private void HackFlags()
        {
            var flagsField = typeof(RealProxy).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance);
            int val = (int)flagsField.GetValue(this);
            val |= 1; // 1 = RemotingProxy, check out System.Runtime.Remoting.Proxies.RealProxyFlags
            flagsField.SetValue(this, val);
        }

        public BrokenProxy(Type t)
            : base(t)
        {
            HackFlags();
        }

        public override IMessage Invoke(IMessage msg)
        {
            var naiar = new NotAnIAsyncResult();
            naiar.SomeProperty = "o noes";
            return new ReturnMessage(naiar, null, 0, null, (IMethodCallMessage)msg);
        }
    }

    interface IRandomInterface
    {
        int DoSomething();
    }

    class Program
    {
        static void Main(string[] args)
        {
            BrokenProxy bp = new BrokenProxy(typeof(IRandomInterface));
            var instance = (IRandomInterface)bp.GetTransparentProxy();
            Func<int> doSomethingDelegate = instance.DoSomething;
            IAsyncResult notAnIAsyncResult = doSomethingDelegate.BeginInvoke(null, null);

            var interfaces = notAnIAsyncResult.GetType().GetInterfaces();
            Console.WriteLine(!interfaces.Any() ? "No interfaces on notAnIAsyncResult" : "Interfaces");
            Console.WriteLine(notAnIAsyncResult is IAsyncResult); // Should be false, is it?!
            Console.WriteLine(((NotAnIAsyncResult)notAnIAsyncResult).SomeProperty);
            Console.WriteLine(((IAsyncResult)notAnIAsyncResult).IsCompleted); // No way this works.
        }
    }
}

Resultado:

No interfaces on notAnIAsyncResult
True
o noes

Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found.
   at System.IAsyncResult.get_IsCompleted()
   at BrokenProxy.Program.Main(String[] args) 

6

Não tenho certeza se você diria que isso é uma estranheza do Windows Vista / 7 ou .Net, mas isso me fez coçar a cabeça por um tempo.

string filename = @"c:\program files\my folder\test.txt";
System.IO.File.WriteAllText(filename, "Hello world.");
bool exists = System.IO.File.Exists(filename); // returns true;
string text = System.IO.File.ReadAllText(filename); // Returns "Hello world."

No Windows Vista / 7, o arquivo será gravado para C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
Este é realmente um aprimoramento de segurança do Vista (não 7, depois). Mas o mais interessante é que você pode ler e abrir o arquivo com o caminho dos arquivos do programa, enquanto que se você procurar no Explorer, não há nada. Este me levou quase um dia de trabalho @ um cliente antes que eu finalmente descobrisse.
Henri

É definitivamente uma coisa do Windows 7 também. Era isso que eu estava usando quando o encontrei. Eu entendo o raciocínio por trás disso, mas ainda era frustrante descobrir.
Spencer Ruport

No Vista / Win 7 (também tecnicamente o winXP), os aplicativos devem gravar em uma pasta AppData na área da pasta Usuários, como dados técnicos do usuário. Os aplicativos não devem gravar nos arquivos de programa / windows / system32 / etc, a menos que tenham privilégios de administrador, e esses privilégios devem existir apenas para dizer atualizar o programa / desinstalar / instalar um novo recurso. MAS! Ainda não escreva para system32 / windows / etc :) Se você executou o código acima como admin (clique com o botão direito do mouse> executar como admin), teoricamente, ele deve gravar na pasta do aplicativo de arquivos do programa.
Steve Syfuhs 31/03/10


6

Você já pensou que o compilador C # poderia gerar CIL inválido? Execute isso e você obterá TypeLoadException:

interface I<T> {
  T M(T p);
}
abstract class A<T> : I<T> {
  public abstract T M(T p);
}
abstract class B<T> : A<T>, I<int> {
  public override T M(T p) { return p; }
  public int M(int p) { return p * 2; }
}
class C : B<int> { }

class Program {
  static void Main(string[] args) {
    Console.WriteLine(new C().M(42));
  }
}

Eu não sei como se sai no compilador C # 4.0.

EDIT : esta é a saída do meu sistema:

C:\Temp>type Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

  interface I<T> {
    T M(T p);
  }
  abstract class A<T> : I<T> {
    public abstract T M(T p);
  }
  abstract class B<T> : A<T>, I<int> {
    public override T M(T p) { return p; }
    public int M(int p) { return p * 2; }
  }
  class C : B<int> { }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine(new C().M(11));
    }
  }

}
C:\Temp>csc Program.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.


C:\Temp>Program

Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli
cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo
ken=null'.
   at ConsoleApplication1.Program.Main(String[] args)

C:\Temp>peverify Program.exe

Microsoft (R) .NET Framework PE Verifier.  Version  3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[token  0x02000005] Type load failed.
[IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x
00000001] Unable to resolve token.
2 Error(s) Verifying Program.exe

C:\Temp>ver

Microsoft Windows XP [Version 5.1.2600]

Obras para me tanto com o compilador C # 3.5 e o C # 4 compilador ...
Jon Skeet

No meu sistema, isso não funciona. Vou colar a saída na pergunta.
Jordão

Ele falhou para mim no .NET 3.5 (não tenho tempo para testar o 4.0). E eu posso replicar o problema com o código VB.NET.
Mark Hurd

3

Há algo realmente empolgante em C #, a maneira como ele lida com fechamentos.

Em vez de copiar os valores da variável de pilha para a variável sem fechamento, ele faz a mágica do pré-processador envolvendo todas as ocorrências da variável em um objeto e, assim, move-a para fora da pilha - diretamente para a pilha! :)

Eu acho que isso torna a linguagem C # ainda mais funcionalmente completa (ou lambda-completa, huh)) do que o próprio ML (que usa o valor da pilha que copia o AFAIK). O F # também possui esse recurso, como o C #.

Isso me traz muito prazer, obrigada pessoal MS!

Não é uma coisa estranha ou de canto ... mas algo realmente inesperado de uma linguagem de VM baseada em pilha :)


3

De uma pergunta que fiz há não muito tempo:

O operador condicional não pode transmitir implicitamente?

Dado:

Bool aBoolValue;

Onde aBoolValueé atribuído Verdadeiro ou Falso;

O seguinte não será compilado:

Byte aByteValue = aBoolValue ? 1 : 0;

Mas isso seria:

Int anIntValue = aBoolValue ? 1 : 0;

A resposta fornecida também é muito boa.


embora eu tenha ve not test it Icerteza de que isso funcionará: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Ou: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar

2
Sim, Alex, isso funcionaria. A chave está na transmissão implícita. 1 : 0sozinho será convertido implicitamente em int, não em Byte.
MPelletier 14/05

2

O escopo em c # é verdadeiramente bizarro às vezes. Permite-me dar um exemplo:

if (true)
{
   OleDbCommand command = SQLServer.CreateCommand();
}

OleDbCommand command = SQLServer.CreateCommand();

Isso falha na compilação, porque o comando é redeclarado? Há algumas suposições interessadas sobre o porquê de funcionar dessa maneira neste tópico no stackoverflow e no meu blog .


34
Eu não vejo isso como particularmente bizarro. O que você chama de "código perfeitamente correto" no seu blog está perfeitamente incorreto de acordo com a especificação do idioma. Pode estar correto em alguma linguagem imaginária que você gostaria que o C # fosse, mas a especificação da linguagem é bastante clara de que no C # é inválido.
31909 Jon Skeet

7
Bem, é válido em C / C ++. E como é C #, eu gostaria que ainda funcionasse. O que mais me incomoda é que não há razão para o compilador fazer isso. Não é difícil fazer o escopo aninhado. Eu acho que tudo se resume ao elemento de menor surpresa. Significando que pode ser que as especificações digam isso e aquilo, mas isso realmente não me ajuda muito se for completamente ilógico que se comporte dessa maneira.
Anders Rune Jensen

6
C #! = C / C ++. Você também gostaria de usar cout << "Hello World!" << endl; em vez de Console.WriteLine ("Hello World!") ;? Também não é ilógico, basta ler as especificações.
Kredns

9
Estou falando de regras de escopo que fazem parte do núcleo da linguagem. Você está falando sobre a biblioteca padrão. Mas agora está claro para mim que eu deveria simplesmente ler a pequena especificação da linguagem c # antes de começar a programar nela.
Anders Rune Jensen

6
Eric Lippert publicou as razões pelas quais o C # foi projetado dessa maneira recentemente: blogs.msdn.com/ericlippert/archive/2009/11/02/… . O resumo é porque é menos provável que as mudanças tenham conseqüências não intencionais.
Helephant
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.