Qual é a pior pegada em C # ou .NET? [fechadas]


377

Eu estava trabalhando recentemente com um DateTimeobjeto e escrevi algo assim:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

A documentação do intellisense AddDays()diz que ele adiciona um dia à data, o que não acontece - na verdade, ele retorna uma data com um dia adicionado, então você deve escrevê-lo como:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

Este já havia me mordido várias vezes antes, então pensei que seria útil catalogar as piores pegadinhas de C #.


157
return DateTime.Now.AddDays (1);
Crashmstr 27/10/08

23
AFAIK, os tipos de valor internos são todos imutáveis, pelo menos em que qualquer método incluído no tipo retorne um novo item em vez de modificar o item existente. Pelo menos, não consigo pensar em alguém que não faça isso: tudo agradável e consistente.
Joel Coehoorn

6
Tipo de valor mutável: System.Collections.Generics.List.Enumerator :( (E sim, você pode vê-lo se comportando estranhamente, se você tentar duro o suficiente.)
Jon Skeet

13
O intellisense fornece todas as informações necessárias. Ele diz que retorna um objeto DateTime. Se apenas alterasse o que você passou, seria um método nulo.
John Kraft

20
Não necessariamente: StringBuilder.Append (...) retorna "this" por exemplo. Isso é bastante comum em interfaces fluentes.
Jon Skeet

Respostas:


304
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo. Seu aplicativo falha sem rastreamento de pilha. Acontece o tempo todo.

(Observe maiúsculas em MyVarvez de minúsculas myVarno getter.)


112
e tão apropriado para este local :)
gbjbaanb

62
Coloquei sublinhados no membro privado, ajuda muito!
chakrit

61
Eu uso propriedades automáticas sempre que posso, pára este tipo de problema monte;)
TWith2Sugars

28
Este é um grande motivo para usar prefixos para os seus campos particulares (existem outros, mas este é um bom): _myVar, m_myVar
jrista

205
@jrista: O Por favor, não ... não m_ ... aargh o horror ...
fretje

254

Type.GetType

O que eu já vi morder muitas pessoas é Type.GetType(string). Eles se perguntam por que isso funciona para tipos em sua própria montagem, e alguns tipos gostam System.String, mas não System.Windows.Forms.Form. A resposta é que ele aparece apenas na montagem atual e dentro mscorlib.


Métodos anônimos

O C # 2.0 introduziu métodos anônimos, levando a situações desagradáveis ​​como esta:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            ThreadStart ts = delegate { Console.WriteLine(i); };
            new Thread(ts).Start();
        }
    }
}

O que isso imprimirá? Bem, depende inteiramente da programação. Ele imprimirá 10 números, mas provavelmente não imprimirá 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, que é o que você pode esperar. O problema é que é a ivariável que foi capturada, não o seu valor no momento da criação do delegado. Isso pode ser resolvido facilmente com uma variável local extra com o escopo correto:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            ThreadStart ts = delegate { Console.WriteLine(copy); };
            new Thread(ts).Start();
        }
    }
}

Execução adiada de blocos de iterador

Esse "teste de unidade do pobre homem" não passa - por que não?

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
    static IEnumerable<char> CapitalLetters(string input)
    {
        if (input == null)
        {
            throw new ArgumentNullException(input);
        }
        foreach (char c in input)
        {
            yield return char.ToUpper(c);
        }
    }

    static void Main()
    {
        // Test that null input is handled correctly
        try
        {
            CapitalLetters(null);
            Console.WriteLine("An exception should have been thrown!");
        }
        catch (ArgumentNullException)
        {
            // Expected
        }
    }
}

A resposta é que o código dentro da fonte do CapitalLetters código não é executado até que o MoveNext()método do iterador seja chamado pela primeira vez.

Tenho outras curiosidades na minha página de quebra-cabeças .


25
O exemplo do iterador é desonesto!
217 Jimmy

8
por que não dividir isso em três respostas para que possamos votar em cada uma delas em vez de todas juntas?
chakrit

13
@chakrit: Em retrospecto, isso provavelmente seria uma boa ideia, mas acho que é tarde demais agora. Ele também poderia ter parecia que eu estava apenas tentando obter mais rep ...
Jon Skeet

19
Na verdade, Type.GetType funciona se você fornecer o AssemblyQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Versão = 3.0.0.0, Cultura = neutra, PublicKeyToken = b77a5c561934e089");
Chilltemp 28/10/08

2
@kentaromiura: A resolução de sobrecarga começa no tipo mais derivado e trabalha na árvore - mas apenas olhando para os métodos originalmente declarados no tipo para o qual está olhando. Foo (int) substitui o método base, portanto não é considerado. Foo (objeto) é aplicável; portanto, a resolução de sobrecarga pára por aí. Estranho, eu sei.
9119 Jon Skeet

194

Lançar exceções novamente

Uma pegadinha que atrai muitos novos desenvolvedores é a semântica de exceção de re-lançamento.

Muito tempo vejo código como o seguinte

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

O problema é que ele limpa o rastreamento da pilha e dificulta muito o diagnóstico, pois não é possível rastrear a origem da exceção.

O código correto é a instrução throw sem argumentos:

catch(Exception)
{
    throw;
}

Ou agrupando a exceção em outra e usando a exceção interna para obter o rastreamento da pilha original:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}

Felizmente, fui ensinado sobre isso na minha primeira semana por alguém e o encontrei no código de desenvolvedores mais antigos. É: catch () {throw; } O mesmo que o segundo trecho de código? catch (exceção e) {throw; } apenas ele não cria um objeto Exception e o preenche?
StuperUser

Além do erro de usar throw ex (ou throw e) em vez de apenas throw, eu tenho que me perguntar que casos existem quando vale a pena capturar uma exceção apenas para lançá-la novamente.
22719 Ryan Lundy

13
@ Kyralessa: existem muitos casos: por exemplo, se você deseja reverter uma transação, antes que o chamador receba a exceção. Você reverte e depois reproduz novamente.
R. Martinho Fernandes

7
Eu vejo isso o tempo todo em que as pessoas capturam e repetem exceções apenas porque são ensinadas a capturar todas as exceções, sem perceber que elas serão capturadas ainda mais na pilha de chamadas. Isso me deixa louco.
James Westgate

5
@ Kyralessa, o maior caso é quando você precisa fazer o log. Log o erro na captura, e relançar ..
Nawfal

194

A janela de inspeção da Heisenberg

Isso pode te incomodar muito se você estiver fazendo coisas sob demanda, como esta:

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

Agora, digamos que você tenha algum código em outro lugar usando isso:

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

Agora você deseja depurar seu CreateMyObj()método. Então você coloca um ponto de interrupção na Linha 3 acima, com a intenção de entrar no código. Apenas para uma boa medida, você também coloca um ponto de interrupção na linha acima _myObj = CreateMyObj();, e até um ponto de interrupção dentroCreateMyObj() si.

O código atinge seu ponto de interrupção na Linha 3. Você entra no código. Você espera inserir o código condicional, porque _myObjobviamente é nulo, certo? Uh ... então ... por que pulou a condição e foi direto parareturn _myObj ?! Você passa o mouse sobre _myObj ... e, de fato, ele tem um valor! Como isso aconteceu?!

A resposta é que seu IDE fez com que ele obtivesse um valor, porque você tem uma janela de observação aberta - especialmente a janela de observação "Autos", que exibe os valores de todas as variáveis ​​/ propriedades relevantes para a linha de execução atual ou anterior. Quando você atingiu seu ponto de interrupção na Linha 3, a janela de inspeção decidiu que você estaria interessado em saber o valor de MyObj- portanto, nos bastidores, ignorando qualquer um de seus pontos de interrupção , ele calculou o valor MyObjpara você - incluindo a chamada para CreateMyObj()isso define o valor de _myObj!

É por isso que eu chamo isso de Janela de inspeção Heisenberg - você não pode observar o valor sem afetá-lo ... :)

PEGUEI VOCÊS!


Editar - Eu sinto que o comentário de @ ChristianHayter merece inclusão na resposta principal, porque parece uma solução alternativa eficaz para esse problema. Então, sempre que você tiver uma propriedade preguiçosa ...

Decore sua propriedade com [DebuggerBrowsable (DebuggerBrowsableState.Never)] ou [DebuggerDisplay ("<loading on demand>")]. - Christian Hayter


10
achado brilhante! você não é um programador, é um depurador real.
isso. __curious_geek

26
Eu me deparei com isso, mesmo passando o mouse sobre a variável, não apenas a janela de inspeção.
Richard Morgan

31
Decore sua propriedade com [DebuggerBrowsable(DebuggerBrowsableState.Never)]ou [DebuggerDisplay("<loaded on demand>")].
Christian Hayter

4
Se você estiver desenvolvendo uma classe de estrutura e desejar a funcionalidade da janela de inspeção sem alterar o comportamento de tempo de execução de uma propriedade construída preguiçosamente, poderá usar um proxy do tipo depurador para retornar o valor, se já estiver construído, e uma mensagem de que a propriedade não foi foi construído se for esse o caso. A Lazy<T>classe (em particular por sua Valuepropriedade) é um exemplo de onde isso é usado.
Sam Harwell

4
Lembro-me de alguém que (por algum motivo não consigo entender) alterou o valor do objeto em uma sobrecarga de ToString. Toda vez que ele pairou sobre ele a dica deu a ele um valor diferente - ele não conseguia entender ...
JNF

144

Aqui está outra vez que me pega:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Seconds é a parte dos segundos do período (2 minutos e 0 segundos tem um valor de segundos 0).

TimeSpan.TotalSeconds é o período inteiro medido em segundos (2 minutos tem um valor total de segundos de 120).


11
Sim, esse também me pegou. Eu acho que deve ser TimeSpan.SecondsPart ou algo para deixar mais claro o que ele representa.
21119 Dan Diplo

3
Ao reler isso, tenho que me perguntar por que TimeSpanainda tem uma Secondspropriedade. Quem dá a mínima para o que é a parte dos segundos de um intervalo de tempo, afinal? É um valor arbitrário, dependente da unidade; Não consigo conceber nenhum uso prático para isso.
MusiGenesis 8/10/10

2
Faz sentido para mim que TimeSpan.TotalSeconds retornaria ... o número total de segundos no intervalo de tempo.
Ed S.

16
@MusiGenesis a propriedade é útil. E se eu quiser exibir o intervalo de tempo dividido em pedaços? Por exemplo, digamos que o seu Timespan represente a duração de '3 horas 15 minutos 10 segundos'. Como você pode acessar essas informações sem as propriedades Segundos, Horas e Minutos?
SolutionYogi

11
Em APIs semelhantes, usei SecondsParte SecondsTotalpara distinguir os dois.
BlueRaja - Danny Pflughoeft

80

Falta de memória porque você não desvinculou eventos.

Isso até chamou a atenção de alguns desenvolvedores seniores.

Imagine um formulário WPF com muitas coisas e, em algum lugar, você se inscreve em um evento. Se você não cancelar a inscrição, o formulário inteiro será mantido na memória após ser fechado e retirado de referência.

Acredito que o problema que vi foi a criação de um DispatchTimer no formulário WPF e a assinatura do evento Tick, se você não fizer um - = no cronômetro, seu formulário vazará memória!

Neste exemplo, seu código de desmontagem deve ter

timer.Tick -= TimerTickEventHandler;

Essa é especialmente complicada desde que você criou a instância do DispatchTimer dentro do formulário WPF, então você pensaria que seria uma referência interna tratada pelo processo de Coleta de Lixo ... infelizmente o DispatchTimer usa uma lista interna estática de assinaturas e serviços solicitações no encadeamento da interface do usuário, portanto, a referência é 'de propriedade' da classe estática.


11
O truque é sempre liberar todas as assinaturas de evento que você criar. Se você começar a confiar no Forms fazendo isso por você, pode ter certeza de que adquirirá o hábito e um dia esquecerá de lançar um evento em algum lugar onde ele precisa ser realizado.
Jason Williams

3
Há uma sugestão do MS-connect para eventos de referência fracos aqui que resolveria esse problema, embora, na minha opinião, devamos substituir inteiramente o modelo de evento incrivelmente ruim por um modelo fracamente acoplado, como o usado pelo CAB.
BlueRaja # Danny Pflughoeft

+1 de mim, obrigado! Bem, não, obrigado pelo trabalho de revisão de código que tive que fazer!
Bob Denny

@ BlueRaja-DannyPflughoeft Com eventos fracos, você tem outra pegada - não é possível assinar lambdas. Você não pode escrever #timer.Tick += (s, e,) => { Console.WriteLine(s); }
22413 Ark-kun

@ Ark-kun sim, lambdas dificulta ainda mais, você teria que salvar seu lambda em uma variável e usá-lo em seu código de desmontagem. Meio que destrói a simplicidade de escrever lambdas, não é?
Timothy Walters

63

Talvez não seja realmente um problema, porque o comportamento está escrito claramente no MSDN, mas quebrou meu pescoço uma vez porque eu achei bastante contra-intuitivo:

Image image = System.Drawing.Image.FromFile("nice.pic");

Esse cara deixa o "nice.pic"arquivo bloqueado até que a imagem seja descartada. Na época, eu achei que seria bom carregar ícones rapidamente e não percebi (no começo) que acabei com dezenas de arquivos abertos e bloqueados! A imagem mantém o controle de onde ele carregou o arquivo ...

Como resolver isso? Eu pensei que um forro faria o trabalho. Eu esperava um parâmetro extra para FromFile(), mas não tinha, então escrevi isso ...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}

10
Eu concordo que esse comportamento não faz sentido. Não consigo encontrar outra explicação a não ser "esse comportamento é intencional".
MusiGenesis 15/10/2009

11
Ah, e o que é ótimo nessa solução alternativa é se você tentar chamar Image.ToStream (eu esqueço o nome exato de imediato) mais tarde, não funcionará.
28710 Joshua

55
precisa verificar algum código. Brb.
Esben Skov Pedersen

7
@EsbenSkovPedersen Um comentário tão simples, mas engraçado e seco. Fez o meu dia.
Inisheer 06/06

51

Se você contar o ASP.NET, eu diria que o ciclo de vida dos formulários da web é um grande problema para mim. Passei inúmeras horas depurando códigos de formulários da web mal escritos, apenas porque muitos desenvolvedores realmente não entendem quando usar qual manipulador de eventos (inclusive eu, infelizmente).


26
É por isso que me mudei para o MVC ... dores de cabeça no estado ...
chakrit 27/10/08

29
Havia toda uma outra questão dedicada especificamente às dicas do ASP.NET (merecidamente). O conceito básico do ASP.NET (fazer com que os aplicativos da Web pareçam aplicativos do Windows para o desenvolvedor) é tão terrivelmente equivocado que eu não tenho certeza se isso conta como uma "pegadinha".
MusiGenesis 28/10/08

11
MusiGenesis Gostaria de poder votar seu comentário centenas de vezes.
Csauve

3
@MusiGenesis Parece errado agora, mas na época as pessoas queriam que seus aplicativos da Web (aplicativos sendo a palavra-chave - o ASP.NET WebForms não foi realmente projetado para hospedar um blog) se comportem da mesma forma que os aplicativos do Windows. Isso mudou recentemente, e muitas pessoas ainda "não estão lá". Todo o problema foi que a abstração era muito gotejante - web não se comportou como um aplicativo de desktop muito que levar a confusão em quase todos.
Luaan 27/03

11
Ironicamente, a primeira coisa que vi sobre o ASP.NET foi um vídeo da Microsoft demonstrando com que facilidade você poderia criar um blog usando o ASP.NET!
MusiGenesis 27/03

51

sobrecarregado == operadores e contêineres não tipados (listas de matrizes, conjuntos de dados etc.):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

Soluções?

  • sempre use string.Equals(a, b)quando estiver comparando tipos de string

  • usando genéricos como List<string>para garantir que ambos os operandos sejam cadeias.


6
Você tem espaços extras lá que fazem tudo errado - mas se você remover os espaços, a última linha ainda será verdadeira, pois "minha" + "sequência" ainda é uma constante.
Jon Skeet

11
ack! você está certo :) ok, eu editei um pouco.
217 Jimmy

um aviso é gerado sobre esses usos.
chakrit

11
Sim, uma das maiores falhas da linguagem C # é o operador == na classe Object. Eles deveriam ter nos forçado a usar o ReferenceEquals.
11119 erikkallen

2
Felizmente, desde a versão 2.0 temos genéricos. Há menos com o que se preocupar se você estiver usando a List <string> no exemplo acima, em vez de ArrayList. Além disso, obtivemos desempenho com isso, sim! Estou sempre pesquisando referências antigas a ArrayLists em nosso código legado.
JoelC

48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

Moral da história: inicializadores de campo não são executados ao desserializar um objeto


8
Sim, eu odeio a serialização do .NET por não executar o construtor padrão. Eu gostaria que fosse impossível construir um objeto sem chamar nenhum construtor, mas infelizmente não é.
Roman Starkov

45

DateTime.ToString ("dd / MM / aaaa") ; Na verdade, isso nem sempre fornece dd / MM / aaaa, mas leva em consideração as configurações regionais e substitui o separador de datas, dependendo de onde você estiver. Então você pode ter dd-MM-aaaa ou algo parecido.

A maneira correta de fazer isso é usar DateTime.ToString ("dd '/' MM '/' aaaa");


DateTime.ToString ("r") deve converter para RFC1123, que usa GMT. GMT está dentro de uma fração de segundo do UTC e, no entanto, o especificador de formato "r" não se converte em UTC , mesmo que o DateTime em questão seja especificado como Local.

Isso resulta na seguinte pegada (varia de acordo com a distância da hora local em relação ao UTC):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

Ops!


19
Mudança de mm para MM - mm é minutos e MM é meses. Outra pegadinha, eu acho ...
Kobi

11
Pude ver como isso seria um problema, se você não soubesse (não sabia) ... mas estou tentando descobrir quando você deseja o comportamento em que está especificamente tentando imprimir uma data que não corresponde às suas configurações regionais.
Beska

6
@Beska: Como você está gravando em um arquivo, ele precisa estar em um formato específico, com um formato de data especificado.
GvS 11/03/10

11
Sou da opinião de que os padrões sendo localizados são piores do que o contrário. Pelo menos o desenvolvedor ignorou a localização completamente, o código funciona em máquinas localizadas de maneira diferente. Dessa forma, o código provavelmente não funciona.
Joshua

32
Na verdade, eu acredito que a maneira correta de fazer isso seriaDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft

44

Vi este postado outro dia, e acho que é bastante obscuro e doloroso para quem não sabe

int x = 0;
x = x++;
return x;

Como isso retornará 0 e não 1, como a maioria esperaria


37
Espero que isso não morda as pessoas - espero que elas não o escrevam em primeiro lugar! (É de qualquer maneira interessante, é claro.)
Jon Skeet

12
Eu não acho que isso é muito obscuro ...
Chris Marasti-Georg

10
Pelo menos, em C #, os resultados são definidos, se inesperados. Em C ++, pode ser 0 ou 1, ou qualquer outro resultado, incluindo o encerramento do programa!
James Curran

7
Isso não é uma pegadinha; x = x ++ -> x = x, então incremento x .... x = ++ x -> incremento x então x = x
Kevin

28
@ Kevin: Eu não acho tão simples assim. Se x = x ++ fosse equivalente a x = x seguido por x ++, o resultado seria x = 1. Em vez disso, acho que o que acontece é primeiro que a expressão à direita do sinal de igual é avaliada (dando 0), então x é incrementado (dando x = 1) e, finalmente, a atribuição é realizada (dando x = 0 mais uma vez).
Tim Goodman

39

Estou um pouco atrasado para esta festa, mas tenho duas dicas que me morderam recentemente:

Resolução DateTime

A propriedade Ticks mede o tempo em 10 milionésimos de segundo (blocos de 100 nanossegundos), no entanto a resolução não é de 100 nanossegundos, é cerca de 15ms.

Este código:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

fornecerá uma saída de (por exemplo):

0
0
0
0
0
0
0
156254
156254
156254

Da mesma forma, se você olhar DateTime.Now.Millisecond, obterá valores em blocos arredondados de 15.625ms: 15, 31, 46, etc.

Esse comportamento específico varia de sistema para sistema , mas há outras dicas relacionadas à resolução nesta API de data / hora.


Path.Combine

Uma ótima maneira de combinar caminhos de arquivos, mas nem sempre se comporta da maneira que você esperaria.

Se o segundo parâmetro começar com um \caractere, ele não fornecerá um caminho completo:

Este código:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

Dá a você esta saída:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

17
A quantização de tempos em intervalos de ~ 15ms não se deve a uma falta de precisão no mecanismo de temporização subjacente (eu esqueci de detalhar isso antes). É porque seu aplicativo está sendo executado em um sistema operacional multitarefa. O Windows faz check-in com seu aplicativo a cada 15ms aproximadamente e, durante o pequeno intervalo de tempo, seu aplicativo processa todas as mensagens que estavam na fila desde a última fatia. Todas as suas chamadas nessa fatia retornam exatamente no mesmo horário, pois são efetivamente feitas no mesmo horário.
MusiGenesis 17/07/2009

2
@MusiGenesis: Eu sei (agora) como isso funciona, mas me parece enganador ter uma medida tão precisa que não é realmente tão precisa. É como dizer que conheço minha altura em nanômetros quando na verdade estou apenas arredondando-a para os dez milhões mais próximos.
1155 Damovisa

7
O DateTime é capaz de armazenar até um único tick; é DateTime. Agora, não está usando essa precisão.
Ruben

16
O '\' extra é uma pegadinha para muitas pessoas unix / mac / linux. No Windows, se houver um '\' inicial, significa que queremos acessar a raiz da unidade (ou seja, C :), tente em um CDcomando para ver o que eu quero dizer .... 1) Vá para C:\Windows\System322) Digite CD \Users3) Woah! Agora você está C:\Users... Entendeu? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") deve retornar, o \Usersque significa precisamente o[current_drive_here]:\Users
chakrit

8
Mesmo sem o 'sono', isso funciona da mesma maneira. Isso não tem nada a ver com o aplicativo ser agendado a cada 15 ms. A função nativa chamada por DateTime.UtcNow, GetSystemTimeAsFileTime, parece ter uma resolução ruim.
Jimbo

38

Quando você inicia um processo (usando System.Diagnostics) que grava no console, mas nunca lê o fluxo Console.Out, após uma certa quantidade de saída, seu aplicativo parece travar.


3
O mesmo ainda pode acontecer quando você redireciona stdout e stderr e usa duas chamadas ReadToEnd em sequência. Para um manuseio seguro de stdout e stderr, é necessário criar um encadeamento de leitura para cada um deles.
Sebastiaan H

34

Nenhum atalho de operador no Linq-To-Sql

Veja aqui .

Em resumo, dentro da cláusula condicional de uma consulta Linq-to-Sql, você não pode usar atalhos condicionais como ||e &&para evitar exceções de referência nula; O Linq-to-Sql avalia os dois lados do operador OR ou AND, mesmo que a primeira condição evite a necessidade de avaliar a segunda condição!


8
TIL. BRB, algumas centenas de consultas LINQ otimizar re-...
tsilb

30

Usando parâmetros padrão com métodos virtuais

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

Saída:
base derivada


10
Estranho, eu pensei que isso é completamente óbvio. Se o tipo declarado for Base, de onde o compilador deve obter o valor padrão, se não Base? Eu pensaria que é um pouco mais difícil entender que o valor padrão pode ser diferente se o tipo declarado for o tipo derivado , mesmo que o método chamado (estaticamente) seja o método base.
Timwi

11
por que uma implementação de um método obteria o valor padrão de outra implementação?
staafl

11
@staafl Os argumentos padrão são resolvidos no tempo de compilação, não no tempo de execução.
fredoverflow

11
Eu diria que essa pegadinha é os parâmetros padrão em geral - as pessoas geralmente não percebem que foram resolvidas no tempo de compilação, em vez de no tempo de execução.
Luaan 27/03

4
@FredOverflow, minha pergunta era conceitual. Embora o comportamento faça sentido na implementação, é pouco intuitivo e uma provável fonte de erros. IMHO, o compilador C # não deve permitir alterar os valores padrão dos parâmetros ao substituir.
staafl

27

Objetos de valor em coleções mutáveis

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

não tem efeito.

mypoints[i]retorna uma cópia de um Pointobjeto de valor. Felizmente, o C # permite modificar um campo da cópia. Silenciosamente, não fazendo nada.


Atualização: isso parece estar corrigido no C # 3.0:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

6
Eu posso ver por que isso é confuso, considerando que realmente funciona com matrizes (ao contrário da sua resposta), mas não com outras coleções dinâmicas, como List <Point>.
Lasse V. Karlsen

2
Você está certo. Obrigado. Corrigi minha resposta :). arr[i].attr=é sintaxe especial para matrizes que você não pode código em recipientes de biblioteca; (Por que (<expressão de valor>) attr = <expr> permitido em tudo o que pode sempre fazer sentido..?
Bjarke Ebert

11
@Bjarke Ebert: Existem alguns casos em que isso faria sentido, mas infelizmente não há como o compilador identificar e permitir isso. Cenário de uso de amostra: um Struct imutável que mantém uma referência a um array bidimensional quadrado junto com um indicador "rotate / flip". A estrutura em si seria imutável; portanto, escrever em um elemento de uma instância somente leitura deve ser bom, mas o compilador não saberá que o configurador de propriedades não vai realmente escrever a estrutura e, portanto, não permitirá .
Supercat

25

Talvez não seja o pior, mas algumas partes da estrutura .net usam graus enquanto outras usam radianos (e a documentação que aparece no Intellisense nunca informa qual, você precisa visitar o MSDN para descobrir)

Tudo isso poderia ter sido evitado com uma Angleaula ...


Estou surpreso este tem tantos upvotes, considerando minhas outras pegadinhas são significativamente pior do que isso
BlueRaja - Danny Pflughoeft

22

Para programadores C / C ++, a transição para C # é natural. No entanto, o maior problema que eu já encontrei pessoalmente (e vi com outros fazendo a mesma transição) não é entender completamente a diferença entre classes e estruturas em C #.

Em C ++, classes e estruturas são idênticas; eles diferem apenas na visibilidade padrão, onde as classes são padronizadas para visibilidade privada e as estruturas padrão para visibilidade pública. Em C ++, essa definição de classe

    class A
    {
    public:
        int i;
    };

é funcionalmente equivalente a esta definição de estrutura.

    struct A
    {
        int i;
    };

No C #, no entanto, classes são tipos de referência, enquanto estruturas são tipos de valor. Isso faz uma GRANDE diferença em (1) decidir quando usar um sobre o outro, (2) testar a igualdade de objetos, (3) desempenho (por exemplo, boxe / unboxing), etc.

Há todos os tipos de informações na web relacionadas às diferenças entre os dois (por exemplo, aqui ). Eu encorajaria qualquer pessoa que fizesse a transição para o C # a ter pelo menos um conhecimento prático das diferenças e suas implicações.


13
Então, o pior problema é que as pessoas não se preocupam em aprender a língua antes de usá-la?
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft Mais como a pegadinha clássica de linguagens aparentemente semelhantes - elas usam palavras-chave muito semelhantes e, em muitos casos, sintaxe, mas funcionam de maneira muito diferente.
Luaan 27/03

19

Coleta de lixo e Dispose (). Embora você não precise fazer nada para liberar memória , ainda precisa liberar recursos via Dispose (). É uma coisa imensamente fácil de esquecer quando você está usando o WinForms ou rastreando objetos de qualquer maneira.


2
O bloco using () resolve perfeitamente esse problema. Sempre que você vir uma chamada para Dispose, poderá refatorar imediatamente e com segurança o uso de ().
Jeremy Frey

5
Acho que a preocupação foi implementar o IDisposable corretamente.
Mark Brackett

4
Por outro lado, o hábito de usar () pode morder você inesperadamente, como ao trabalhar com o PInvoke. Você não deseja descartar algo que a API ainda está fazendo referência.
MusiGenesis 28/10/08

3
A implementação correta do IDisposable é muito difícil de entender e até mesmo os melhores conselhos que encontrei sobre isso (Diretrizes do .NET Framework) podem ser confusos para aplicar até que você finalmente "entenda".
Quibblesome 23/10/09

11
O melhor conselho que já encontrado na IDisposable vem de Stephen Cleary, incluindo três regras simples e um artigo detalhado sobre IDisposable
Roman Starkov

19

Implementar matrizes IList

Mas não o implemente. Quando você liga para Adicionar, ele diz que não funciona. Então, por que uma classe implementa uma interface quando não pode suportá-la?

Compila, mas não funciona:

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

Temos muito esse problema, porque o serializador (WCF) transforma todas as ILists em matrizes e obtemos erros de tempo de execução.


8
IMHO, o problema é que a Microsoft não possui interfaces suficientes definidas para coleções. IMHO, ele deve ter iEnumerable, iMultipassEnumerable (suporta Redefinir e garante que várias passagens correspondam), iLiveEnumerable (teria semântica parcialmente definida se a coleção mudar durante a enumeração - as alterações podem ou não aparecer na enumeração, mas não devem causar resultados ou exceções falsos), iReadIndexable, iReadWriteIndexable, etc. Como as interfaces podem "herdar" outras interfaces, isso não teria acrescentado muito trabalho extra, se houver (ele salvaria stubs não implementados).
Supercat 19/11

@ supercat, isso seria confuso demais para iniciantes e certos programadores de longa data. Eu acho que as coleções .NET e suas interfaces são maravilhosamente elegantes. Mas aprecio sua humildade. ;)
Jordânia

@ Jordan: Desde que escrevi o texto acima, decidi que uma abordagem melhor seria ter ambos IEnumerable<T>e IEnumerator<T>oferecer suporte a uma Featurespropriedade, além de alguns métodos "opcionais" cuja utilidade seria determinada pelo que "Recursos" relatou. No entanto, defendo meu ponto principal, que é o de que há casos em que o código que recebe um IEnumerable<T>precisará de promessas mais fortes do que o previsto IEnumerable<T>. Telefonar ToListrenderia um IEnumerable<T>que cumprisse tais promessas, mas em muitos casos seria desnecessariamente caro. Eu diria que deveria haver ... #
308

... um meio pelo qual o código que recebe um IEnumerable<T>pode fazer uma cópia do conteúdo, se necessário, mas pode se abster de fazê-lo desnecessariamente.
precisa

Sua opção não é absolutamente legível. Quando vejo um IList no código, sei com o que estou trabalhando, em vez de ter que analisar uma propriedade Features. Os programadores gostam de esquecer que um recurso importante do código é que ele pode ser lido por pessoas e não apenas por computadores. O espaço para nome das coleções .NET não é ideal, mas é bom, e às vezes encontrar a melhor solução não é uma questão de ajustar um princípio de maneira mais ideal. Alguns dos piores códigos com os quais trabalhei foram os que tentavam se encaixar no DRY idealmente. Raspei e reescrevi. Era apenas um código ruim. Eu não gostaria de usar sua estrutura.
Jordânia

18

o foreach loops o escopo das variáveis!

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

imprime cinco "amet", enquanto o exemplo a seguir funciona bem

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());

11
Isso é essencialmente equivalente ao exemplo de Jon com métodos anônimos.
Mehrdad Afshari

3
Salve que é ainda mais confuso com o foreach, onde a variável "s" é mais fácil de misturar com a variável de escopo. Com for-loops comuns, a variável index é claramente a mesma para cada iteração.
Mikko Rantanen

2
blogs.msdn.com/ericlippert/archive/2009/11/12/… e sim, gostaria que a variável tivesse o escopo "adequadamente".
Roman Starkov

2

Você está basicamente imprimindo a mesma variável repetidamente sem alterá-la.
Jordânia

18

O MS SQL Server não pode manipular datas antes de 1753. Significativamente, isso está fora de sincronia com a DateTime.MinDateconstante .NET , que é 1/1/1. Então, se você tentar salvar um estado de espírito, uma data incorreta (como aconteceu recentemente em uma importação de dados) ou simplesmente a data de nascimento de William, o Conquistador, você estará em apuros. Não há solução interna para isso; se é provável que você precise trabalhar com datas anteriores a 1753, precisará escrever sua própria solução alternativa.


17
Francamente, acho que o MS SQL Server tem esse direito e o .NET está errado. Se você fizer a pesquisa, saberá que as datas anteriores a 1751 ficam estranhas devido a alterações no calendário, dias completamente ignorados etc. A maioria dos RDBMs tem algum ponto de corte. Isso deve lhe dar um ponto de partida: ancestry.com/learn/library/article.aspx?article=3358
NotMe

11
Além disso, a data é 1753. Que foi praticamente a primeira vez que temos um calendário contínuo sem que as datas sejam ignoradas. O SQL 2008 introduziu o tipo de dados Date e datetime2, que pode aceitar datas de 1/1/01 a 31/12/9999. No entanto, as comparações de datas usando esses tipos devem ser vistas com suspeita se você estiver realmente comparando datas anteriores a 1753.
NotMe

Ah, certo, 1753, corrigido, obrigado.
Shaul Behr

Realmente faz sentido fazer comparações de datas com essas datas? Quero dizer, para o History Channel isso faz muito sentido, mas não me vejo querendo saber o dia exato da semana em que a América foi descoberta.
Camilo Martin

5
Através da Wikipedia, em Julian Day, você pode encontrar um programa básico de 13 linhas, CALJD.BAS, publicado em 1984, que pode fazer cálculos de data de cerca de 5000 aC, levando em conta os dias bissextos e os dias ignorados em 1753. Portanto, não vejo por que "os modernos "sistemas como o SQL2008 devem fazer pior. Você pode não estar interessado em uma representação de data correta no século 15, mas outras podem, e nosso software deve lidar com isso sem erros. Outra questão é o salto de segundos. . .
Roland

18

O desagradável Linq Caching Gotcha

Veja minha pergunta que levou a essa descoberta e o blogueiro que descobriu o problema.

Em resumo, o DataContext mantém um cache de todos os objetos Linq para Sql que você já carregou. Se alguém fizer alguma alteração em um registro carregado anteriormente, você não poderá obter os dados mais recentes, mesmo se recarregar explicitamente o registro!

Isso ocorre devido a uma propriedade chamada ObjectTrackingEnabledno DataContext, que por padrão é verdadeira. Se você definir essa propriedade como false, o registro será carregado novamente toda vez ... MAS ... você não poderá manter nenhuma alteração nesse registro com SubmitChanges ().

PEGUEI VOCÊS!


Iv só passei um dia e meio perseguir este BUG ... (e um monte de cabelo!)
Surgical Coder

Isso é chamado de conflito de simultaneidade e ainda hoje é uma pegadinha, embora existam certas maneiras de contornar isso agora, embora elas tendam a ser um pouco pesadas. DataContext foi um pesadelo. O_o
Jordânia

17

O contrato no Stream.Read é algo que eu já vi provocar muitas pessoas:

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

A razão pela qual isso está errado é que Stream.Readlerá no máximo o número especificado de bytes, mas é totalmente gratuito para ler apenas 1 byte, mesmo que outros 7 bytes estejam disponíveis antes do final do fluxo.

Não ajuda que isso seja tão parecido com Stream.Write, que é garantido que todos os bytes foram gravados se retornar sem exceção. Também não ajuda que o código acima funcione quase o tempo todo . E é claro que não ajuda que não exista um método conveniente e pronto para ler exatamente N bytes corretamente.

Portanto, para tapar o buraco e aumentar a conscientização disso, aqui está um exemplo de uma maneira correta de fazer isso:

    /// <summary>
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.</returns>
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// <summary>
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }

11
Ou, no seu exemplo explícito: var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader tem um FillBuffermétodo muito ...
jimbobmcgee

15

Eventos

Eu nunca entendi por que os eventos são um recurso de linguagem. Eles são complicados de usar: você precisa verificar se há nulo antes de ligar, você precisa cancelar o registro (você mesmo), não consegue descobrir quem está registrado (por exemplo: eu me registrei?). Por que um evento não é apenas uma aula na biblioteca? Basicamente um especialista List<delegate>?


11
Além disso, multithreading é doloroso. Todos esses problemas, exceto o nulo, são corrigidos no CAB (cujos recursos realmente devem ser incorporados ao idioma) - os eventos são declarados globalmente e qualquer método pode se declarar um "assinante" de qualquer evento. Meu único problema com o CAB é que os nomes de eventos globais são seqüências de caracteres em vez de enumerações (que podem ser corrigidas por enumerações mais inteligentes, como Java, que inerentemente funcionam como sequências de caracteres!) . É difícil configurar o CAB, mas há um clone de código aberto simples disponível aqui .
BlueRaja - Danny Pflughoeft

3
Não gosto da implementação de eventos .net. A assinatura de eventos deve ser tratada chamando um método que adicione a assinatura e retorne um IDisposable que, quando descartado, excluirá a assinatura. Não há necessidade de uma construção especial combinando um método "add" e "remove" cuja semântica possa ser um pouco desonesta, especialmente se alguém tentar adicionar e remover posteriormente um delegado multicast (por exemplo, adicione "B" seguido de "AB" e remova "B" (saindo de "BA") e "AB" (ainda saindo de "BA"). Ops.
supercat

@supercat Como você reescreveu button.Click += (s, e) => { Console.WriteLine(s); }?
Arca-kun

Se eu precisasse cancelar a inscrição separadamente de outros eventos IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});e cancelar a inscrição via clickSubscription.Dispose();. Se meu objeto manter todas as assinaturas por toda a vida útil, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));e depois MySubscriptions.Dispose()matar todas as assinaturas.
Supercat 19/10

@ Ark-kun: Ter que manter objetos que encapsulem assinaturas externas pode parecer um incômodo, mas considerar as assinaturas como entidades tornaria possível agregá-las a um tipo que garanta que todas elas sejam limpas, algo que de outra forma é muito difícil.
Supercat #

14

Hoje eu consertei um bug que escapava por muito tempo. O bug estava em uma classe genérica usada no cenário multithread e um campo int estático foi usado para fornecer sincronização sem bloqueio usando o Interlocked. O bug foi causado porque cada instanciação da classe genérica para um tipo tem sua própria estática. Portanto, cada encadeamento tem seu próprio campo estático e não foi usado um bloqueio como pretendido.

class SomeGeneric<T>
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric<int>.i = 5;
        SomeGeneric<string>.i = 10;
        Console.WriteLine(SomeGeneric<int>.i);
        Console.WriteLine(SomeGeneric<string>.i);
        Console.WriteLine(SomeGeneric<int>.i);
    }
}

Imprime 5 10 5


5
você pode ter uma classe base não genérica, que define a estática e herda os genéricos dela. Embora eu nunca tenha caído nesse comportamento em C # - ainda me lembro das longas horas de depuração de alguns modelos de C ++ ... Eww! :)
Paulius

7
Estranho, eu pensei que isso era óbvio. Basta pensar no que deveria fazer se itivesse o tipo T.
Timwi

11
O parâmetro type faz parte do Type. SomeGeneric<int>é um tipo diferente de SomeGeneric<string>; então é claro que cada um tem a sua própriapublic static int i
radarbob

13

Enumeráveis ​​podem ser avaliados mais de uma vez

Ele vai te morder quando você tem um enumerável preguiçosamente enumerado e você o itera duas vezes e obtém resultados diferentes. (ou você obtém os mesmos resultados, mas é executado duas vezes desnecessariamente)

Por exemplo, enquanto escrevia um determinado teste, eu precisava de alguns arquivos temporários para testar a lógica:

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

Imagine minha surpresa quando File.Delete(file)joga FileNotFound!!

O que está acontecendo aqui é que o filesenumerável foi iterado duas vezes (os resultados da primeira iteração simplesmente não são lembrados) e em cada nova iteração você estava chamando novamente Path.GetTempFilename()para obter um conjunto diferente de nomes de arquivos temporários.

A solução é, obviamente, enumerar ansiosamente o valor usando ToArray()ou ToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

Isso é ainda mais assustador quando você está fazendo algo multiencadeado, como:

foreach (var file in files)
    content = content + File.ReadAllText(file);

e você descobre que content.Lengthainda é 0 depois de todas as gravações !! Você então começa a checar rigorosamente se não tem uma condição de corrida quando ... depois de uma hora desperdiçada ... você descobriu que é apenas aquela coisinha que você esqueceu ...


Isso ocorre por design. É chamado de execução adiada. Entre outras coisas, pretende simular construções TSQL. Toda vez que você seleciona em uma visualização sql, obtém resultados diferentes. Ele também permite o encadeamento, útil para armazenamentos de dados remotos, como o SQL Server. Caso contrário x.Select.Where.OrderBy iria enviar 3 comandos separados para o banco de dados ...
as9876

@AYS você perdeu a palavra "Gotcha" no título da pergunta?
chakrit

Eu pensei que pegadinha significava uma supervisão dos designers, não algo intencional.
As9876

Talvez deva haver outro tipo para IEnumerables não reinicializáveis. Como, AutoBufferedEnumerable? Pode-se implementá-lo facilmente. Esse problema parece principalmente devido à falta de conhecimento do programador, não acho que haja algo errado com o comportamento atual.
Eldritch Conundrum

13

Achei um estranho que me deixou preso na depuração por um tempo:

Você pode incrementar nulo para um int nulo sem gerar uma exceção e o valor permanece nulo.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null

Esse é o resultado de como o C # utiliza operações para tipos anuláveis. É um pouco semelhante ao NaN consumindo tudo o que você joga nele.
IllidanS4 quer Monica de volta 23/02

10
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

Sim, esse comportamento está documentado, mas isso certamente não o torna correto.


5
Eu discordo - quando uma palavra está em maiúsculas, pode ter um significado especial que você não quer atrapalhar o caso do título, por exemplo, "presidente dos EUA" -> "Presidente dos EUA", não "Presidente dos EUA" EUA".
Shaul Behr

5
@Shaul: Nesse caso, eles devem especificar isso como um parâmetro para confusão evitar, porque eu nunca conheci ninguém que esperava este comportamento antes do tempo - o que torna esta uma pegadinha !
BlueRaja # Danny Pflughoeft 5/05
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.