web-dev-qa-db-fra.com

Quel est le cas de coin le plus étrange que vous ayez vu en C # ou .NET?

Je collectionne quelques cas de coin et casse-tête et j'aimerais toujours en entendre davantage. La page ne couvre vraiment que les bits et les bobs du langage C #, mais je trouve aussi les choses fondamentales de .NET intéressantes. Par exemple, en voici un qui ne figure pas sur la page, mais que je trouve incroyable:

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

Je m'attendrais à ce que pour imprimer Faux - après tout, "nouveau" (avec un type de référence) crée toujours un nouvel objet , n'est-ce pas? Les spécifications de C # et de la CLI indiquent que cela devrait être le cas. Eh bien, pas dans ce cas particulier. Il imprime True et l’a fait sur toutes les versions du framework avec lequel je l’ai testé. (Je ne l'ai pas essayé sur Mono, certes ...)

Soyons clairs: ce n’est qu’un exemple du genre de chose que je cherche - je ne cherchais pas particulièrement une discussion/explication de cette bizarrerie. (Ce n'est pas la même chose qu'un internat de chaîne normal; en particulier, l'internement de chaîne ne se produit normalement pas lorsqu'un constructeur est appelé.) Je demandais vraiment un comportement étrange similaire.

Y a-t-il d'autres joyaux qui se cachent là-bas?

322
Jon Skeet

Je pense que je vous ai déjà montré celui-ci auparavant, mais j'aime le plaisir ici - cela a pris du débogage à retrouver! (le code original était évidemment plus complexe et subtil ...)

    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());
    }

Alors qu'est-ce que ...

Réponse: tout Nullable<T> - tel que int?. Toutes les méthodes sont remplacées, à l'exception de GetType () qui ne peut pas l'être; il est donc cast (boxed) à object (et donc à null) pour appeler object.GetType () ... qui appelle null ;-p


Mise à jour: l'intrigue s'épaissit ... Ayende Rahien jeta un défi similaire sur son blog , mais avec un 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?");
}

Mais cela peut être vaincu! En utilisant la même indirection utilisée par des choses comme la communication à distance; warning - ce qui suit est le mal pur :

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

Ceci mis en place, l'appel new() est redirigé vers le proxy (MyFunnyProxyAttribute), qui renvoie null. Maintenant va te laver les yeux!

394
Marc Gravell

Arrondi de banquiers.

Celui-ci n'est pas vraiment un bug ou un dysfonctionnement du compilateur, mais certainement un étrange cas de coin ...

Le .Net Framework utilise un schéma ou arrondi connu sous le nom d'arrondissement bancaire.

Dans Bankers 'Rounding, les chiffres de 0,5 sont arrondis au nombre pair le plus proche.

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

Cela peut conduire à des erreurs inattendues dans les calculs financiers basés sur l'arrondi plus connu Round-Half-Up.

Cela est également vrai de Visual Basic.

216
Samuel Kim

Que fera cette fonction si elle est appelée Rec(0) (pas sous le débogueur)?

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

Répondre:

  • Sur JIT 32 bits, cela devrait entraîner une exception StackOverflowException.
  • Sur JIT 64 bits, il devrait imprimer tous les nombres dans int.MaxValue

En effet, le compilateur JIT 64 bits applique l'optimisation d'appel final , alors que le JIT 32 bits ne le fait pas.

Malheureusement, je ne dispose pas d'un ordinateur 64 bits pour vérifier cela, mais la méthode remplit toutes les conditions pour l'optimisation de l'appel final. Si quelqu'un en a un, je serais intéressé de voir si c'est vrai.

176
Greg Beech

Attribuer ceci!


C’est une question que j’aime poser lors des fêtes (c’est probablement la raison pour laquelle je ne suis plus invité):

Pouvez-vous compiler le code suivant?

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

Une triche facile pourrait être:

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

Mais la vraie solution est la suivante:

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

C'est donc un fait peu connu que les types de valeur (structs) peuvent réaffecter leur variable this.

111
Omer Mor

Il y a quelques années, lorsque nous travaillions sur le programme de fidélité, nous avions un problème avec le nombre de points accordés aux clients. Le problème était lié à la conversion/conversion double en int.

En code ci-dessous:

double d = 13.6;

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

est-ce que i1 == i2?

Il s'avère que i1! = I2. En raison des différentes politiques d'arrondi dans Convert et opérateur de conversion, les valeurs réelles sont:

i1 == 14
i2 == 13

Il est toujours préférable d'appeler Math.Ceiling () ou Math.Floor () (ou Math.Round avec MidpointRounding qui répond à nos exigences)

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

Ils devraient avoir fait un entier égal à 0 même s'il y a une surcharge de la fonction enum.

Je connaissais les raisons pour lesquelles l'équipe de base C # justifiait de mapper 0 en enum, mais néanmoins, ce n'est pas aussi orthogonal qu'il devrait l'être. Exemple de Npgsql .

Exemple de test:

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");
        }
    }
}
74
Michael Buen

C’est l’un des plus inhabituels que j’ai vu jusqu’à présent (mis à part ceux d’ici bien sûr!):

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

Il vous permet de le déclarer mais n’a pas de véritable utilité, car il vous demandera toujours d’emballer la classe que vous bourrez au centre avec une autre tortue.

[blague] Je suppose que c'est des tortues tout en bas ... [/ blague]

67
RCIX

En voici un que je n'ai découvert que récemment ...

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

Ce qui précède semble fou à première vue, mais est réellement légal . Non, vraiment (même si j’ai manqué un élément clé, mais n’est rien comme "ajouter une classe appelée IFoo" ou "ajouter un alias using pour pointer IFoo vers une classe").

Voyez si vous pouvez comprendre pourquoi, alors: Qui a dit que vous ne pouviez pas instancier une interface?

65
Marc Gravell

Quand un booléen n'est-il ni vrai ni faux?

Bill a découvert que vous pouvez pirater un booléen de sorte que si A est vrai et B est vrai, (A et B) est faux.

Booléens piratés

56
Jonathan Allen

J'arrive un peu en retard à la fête, mais j'ai troisquatre cinq:

  1. Si vous interrogez InvokeRequired sur un contrôle qui n'a pas été chargé/affiché, il dira false - et exploserait à la figure si vous essayez de le changer depuis un autre thread ( la solution est de faire référence à cela .Handle dans le créateur du contrôle).

  2. Un autre qui m'a fait trébucher est celui donné à une Assemblée avec:

    enum MyEnum
    {
        Red,
        Blue,
    }
    

    si vous calculez MyEnum.Red.ToString () dans un autre assembly et que, dans l'intervalle, quelqu'un a recompilé votre enum pour:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }
    

    au moment de l'exécution, vous obtiendrez "Black".

  3. J'avais une assemblée partagée avec des constantes bien pratiques. Mon prédécesseur avait laissé une foule de propriétés de type "get-only" ("get-only"), je pensais que je devais me débarrasser de l'encombrement et utiliser simplement const. J'étais plus qu'un peu surpris lorsque VS les a compilés en fonction de leurs valeurs et non de leurs références.

  4. Si vous implémentez une nouvelle méthode d'interface à partir d'un autre assembly, mais que vous reconstruisez en vous référant à l'ancienne version de cet assembly, vous obtenez une typeLoadException (aucune implémentation de 'NewMethod'), même si vous avez l'a implémenté (voir ici ).

  5. Dictionnaire <,>: "L'ordre dans lequel les éléments sont retournés n'est pas défini". Ceci est horrible , car il peut parfois vous mordre, mais travaillez avec d’autres, et si vous venez de supposer aveuglément que Dictionary va jouer à Nice (" pourquoi pas? pensais-je, la liste fait "), vous devez vraiment avoir le nez dans le nez avant de commencer à remettre en question votre hypothèse.

47
Benjol

VB.NET, nullables et l'opérateur ternaire:

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

Cela m'a pris du temps à déboguer, car je m'attendais à ce que i contienne Nothing.

Que contient vraiment? 0.

Ceci est surprenant mais en réalité, le comportement "correct": Nothing dans VB.NET n’est pas exactement identique à null dans CLR: Nothing peut signifier null ou default(T) pour un type de valeur T, en fonction du contexte. Dans le cas ci-dessus, If infère Integer comme type commun de Nothing et 5, de sorte que, dans ce cas, Nothing signifie 0 .

33
Heinzi

J'ai trouvé un deuxième cas de coin vraiment étrange qui bat mon premier par un long coup.

La méthode String.Equals (String, String, StringComparison) n'est pas réellement exempte d'effet secondaire.

Je travaillais sur un bloc de code qui avait cela sur une ligne au sommet d'une fonction:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

La suppression de cette ligne entraîne un débordement de pile ailleurs dans le programme.

Le code s’est avéré installer un gestionnaire pour ce qui était essentiellement un événement BeforeAssemblyLoad et tenter de le faire.

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

À présent, je ne devrais pas avoir à vous dire. L'utilisation d'une culture qui n'a pas encore été utilisée dans une comparaison de chaînes entraîne une charge d'assemblage. InvariantCulture n'est pas une exception à cela.

28
Joshua

Voici un exemple de la manière dont vous pouvez créer une structure qui provoque le message d'erreur "Tentative de lecture ou d'écriture en mémoire protégée. Cela indique souvent qu'une autre mémoire est corrompue". La différence entre le succès et l'échec est très subtile.

Le test unitaire suivant illustre le problème.

Voyez si vous pouvez trouver ce qui ne va pas.

    [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);
        }
    }
20
cbp

C # prend en charge les conversions entre tableaux et listes tant que les tableaux ne sont pas multidimensionnels et qu'il existe une relation d'héritage entre les types et que les types sont des types de référence

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" };

Notez que cela ne fonctionne pas:

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[]'
18

C'est le plus étrange que j'ai rencontré par accident:

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

Utilisé comme suit:

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

Va jeter un NullReferenceException. Il s'avère que les additions multiples sont compilées par le compilateur C # en un appel à String.Concat(object[]). Avant .NET 4, il existait un bogue dans cette surcharge de Concat où l'objet était vérifié comme null, mais pas dans le résultat 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;

Ceci est un bug de ECMA-334 §14.7.4:

L'opérateur binaire + effectue la concaténation de chaînes lorsqu'un ou les deux opérandes sont de type string. Si un opérande de concaténation de chaîne est null, une chaîne vide est remplacée. Sinon, tout opérande non-chaîne est converti en sa représentation sous forme de chaîne en appelant la méthode virtuelle ToString héritée du type object. Si ToString renvoie null, une chaîne vide est remplacée.

15
Sam Harwell

Intéressant - quand j'ai commencé à comprendre que je pensais que le compilateur C # cherchait quelque chose, mais même si vous émettez l'IL directement pour supprimer tout risque d'interférence, cela se produit toujours, ce qui signifie qu'il s'agit vraiment de la newobj op -code qui fait la vérification.

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);

Cela équivaut également à true si vous cochez string.Empty, ce qui signifie que ce code d'opération doit avoir un comportement spécial pour internaliser des chaînes vides.

12
Greg Beech

Et si vous avez une classe générique qui a des méthodes qui pourraient être rendues ambiguës en fonction des arguments de type? J'ai récemment rencontré cette situation en écrivant un dictionnaire à double sens. Je voulais écrire des méthodes symétriques Get() qui renverraient le contraire de l'argument transmis. Quelque chose comme ça:

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

Tout va bien si vous créez une instance où T1 et T2 sont de types différents:

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

Mais si T1 et T2 sont identiques (et probablement si l'une était une sous-classe d'une autre), c'est une erreur du compilateur:

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

Fait intéressant, toutes les autres méthodes dans le second cas sont toujours utilisables; ce ne sont que des appels à la méthode maintenant ambiguë qui provoque une erreur du compilateur. Cas intéressant, si un peu improbable et obscur.

10
tclem

C # Accessibilité Puzzler


La classe dérivée suivante accède à un champ privé à partir de sa classe de base et le compilateur regarde en silence de l'autre côté:

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

Le domaine est bien privé:

private int m_basePrivateField = 0;

Attention à deviner comment on peut faire compiler un tel code?

.

.

.

.

.

.

.

Répondre


L'astuce consiste à déclarer Derived comme une classe interne de Base:

public class Base
{
    private int m_basePrivateField = 0;

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

Les classes intérieures ont un accès complet aux membres de la classe externe. Dans ce cas, la classe interne dérive également de la classe externe. Cela nous permet de "casser" l'encapsulation des membres privés.

10
Omer Mor

Je viens de trouver une belle petite chose aujourd'hui:

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

Cela jette une erreur de compilation.

L'appel à la méthode 'Initialize' doit être distribué de manière dynamique, mais ne peut pas l'être car il fait partie d'une expression d'accès de base. Envisagez de transtyper les arguments dynamiques ou d'éliminer l'accès de base.

Si j'écris base.Initialize (en tant qu'objet); cela fonctionne parfaitement, cependant cela semble être un "mot magique" ici, puisqu'il fait exactement la même chose, tout est toujours reçu comme dynamique ...

10
TDaver
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

Le résultat est "Tentative de lecture de la mémoire protégée. Indique qu'une autre mémoire est corrompue".

10
Joshua

PropertyInfo.SetValue () peut affecter ints à enums, ints à nullable ints, enums à nullable, mais pas à inables.

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 !!!

Description complète ici

10
Anders Ivner

Dans une API que nous utilisons, les méthodes qui renvoient un objet de domaine peuvent renvoyer un "objet null" spécial. Dans l'implémentation de ceci, l'opérateur de comparaison et la méthode Equals() sont remplacés pour renvoyer true s'il est comparé à null.

Ainsi, un utilisateur de cette API peut avoir un code comme celui-ci:

_return test != null ? test : GetDefault();
_

ou peut-être un peu plus verbeux, comme ceci:

_if (test == null)
    return GetDefault();
return test;
_

GetDefault() est une méthode renvoyant une valeur par défaut que nous souhaitons utiliser à la place de null. La surprise m’a frappé lorsque j’utilisais ReSharper et qu’il était recommandé de réécrire l’un des éléments suivants:

_return test ?? GetDefault();
_

Si l'objet de test est un objet null renvoyé par l'API au lieu d'un null correct, le comportement du code a maintenant changé, car l'opérateur de coalescence nul recherche en fait null sans exécuter _operator=_ ou Equals().

8
Tor Livar

Considérez ce cas étrange:

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

Si Base et Derived sont déclarés dans le même assemblage, le compilateur rendra Base::Method virtuel et scellé (dans le CIL), même si Base n'implémente pas l'interface. .

Si Base et Derived se trouvent dans des assemblys différents, lors de la compilation de l'assembly Derived, le compilateur ne modifiera pas l'autre assemblage, de sorte qu'il introduira un membre dans Derived qui sera une implémentation explicite pour MyInterface::Method qui déléguera simplement l'appel à Base::Method.

Le compilateur doit le faire afin de prendre en charge la répartition polymorphe en ce qui concerne l’interface, c’est-à-dire qu’il doit rendre cette méthode virtuelle.

8
Jordão

Ce qui suit pourrait être une connaissance générale qui me manquait tout simplement, mais hein. Il y a quelque temps, nous avions un cas de bogue incluant des propriétés virtuelles. En résumant un peu le contexte, considérez le code suivant et appliquez un point d'arrêt à la zone spécifiée:

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 { } }
}

Lorsque vous vous trouvez dans le contexte d'objet Derived, vous pouvez obtenir le même comportement lorsque vous ajoutez base.Property en tant que surveillance ou que vous saisissez base.Property dans le quickwatch.

Il m'a fallu du temps pour comprendre ce qui se passait. Finalement, le Quickwatch m'a éclairé. Lorsque vous accédez à Quickwatch et explorez l'objet Derived (ou depuis le contexte de l'objet, this) et que vous sélectionnez le champ base, le champ d'édition en haut de Quickwatch affiche la distribution suivante :

((TestProject1.Base)(d))

Ce qui signifie que si la base est remplacée en tant que telle, l’appel sera

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

pour les montres, Quickwatch et les info-bulles de débogage à la souris, il serait alors judicieux d’afficher "AWESOME" au lieu de "BASE_AWESOME" lorsqu’il s’agit de polymorphisme. Je ne sais toujours pas pourquoi cela le transformerait en une distribution, une hypothèse est que call pourrait ne pas être disponible à partir du contexte de ces modules, et uniquement callvirt.

Quoi qu'il en soit, cela ne change évidemment rien en termes de fonctionnalité, Derived.BaseProperty retournera toujours réellement "BASE_AWESOME", et ce ne fut donc pas la racine de notre bogue au travail, mais simplement un composant déroutant. Cependant, j'ai trouvé intéressant de voir comment cela pourrait induire en erreur les développeurs qui l'ignoreraient lors de leurs sessions de débogage, en particulier si Base n'est pas exposé dans votre projet mais plutôt référencé comme une DLL tierce, ce qui a pour conséquence que les développeurs disent :

"Oi, attendez..que? Omg que DLL ressemble à .. faire quelque chose de drôle"

7
Dynami Le Savard

Celui-ci est assez difficile à atteindre. Je l'ai rencontré alors que j'essayais de construire une implémentation RealProxy qui prend réellement en charge Begin/EndInvoke (merci à MS d'avoir rendu cela impossible à faire sans horribles piratages). Il s’agit en gros d’un bogue dans le CLR. Le chemin de code non géré pour BeginInvoke ne permet pas de confirmer que le message de retour de RealProxy.PrivateInvoke (et mon remplacement Invoke) renvoie une instance d’un IAsyncResult. Une fois qu'il est retourné, le CLR devient incroyablement confus et perd toute idée de ce qui se passe, comme le montrent les tests ci-dessous.

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.
        }
    }
}

Sortie:

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) 
7
Steve

Je ne sais pas si vous diriez que c’est une bizarrerie de Windows Vista/7 ou .Net, mais cela m’a ébranlé la tête pendant un moment.

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."

Sous Windows Vista/7, le fichier sera en réalité écrit sur C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt

6
Spencer Ruport

Avez-vous déjà pensé que le compilateur C # pourrait générer un CIL invalide? Exécutez ceci et vous obtiendrez un 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));
  }
}

Je ne sais pas comment cela se passe dans le compilateur C # 4.0 cependant.

MODIFIER: voici la sortie de mon système:

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]
6
Jordão

D'une question que j'ai posée il n'y a pas longtemps:

L'opérateur conditionnel ne peut pas convertir implicitement?

Donné:

Bool aBoolValue;

aBoolValue est attribué à True ou False;

Les éléments suivants ne seront pas compilés:

Byte aByteValue = aBoolValue ? 1 : 0;

Mais ceci:

Int anIntValue = aBoolValue ? 1 : 0;

La réponse fournie est très bonne aussi.

3
MPelletier

C # a quelque chose de vraiment excitant, la façon dont il gère les fermetures.

Au lieu de copier les valeurs de variable de pile dans la variable de fermeture, il effectue la magie du préprocesseur en encapsulant toutes les occurrences de la variable dans un objet et le déplace ainsi hors de la pile - directement dans le tas! :)

Je suppose que cela fait de C # un langage encore plus fonctionnel (ou lambda-complet, hein) que ML lui-même (qui utilise la copie de valeur de pile autant que je sache). F # a aussi cette fonctionnalité, comme C #.

Cela me fait vraiment plaisir, merci les gars MS!

Ce n'est pas un cas étrange ou un coin cependant ... mais quelque chose de vraiment inattendu d'un langage de pile VM :)

3
Bubba88

voici quelques-unes des miennes:

  1. cela peut être nul lors de l'appel d'une méthode d'instance sans exception NullReferenceException.
  2. une valeur d'énumération par défaut ne doit pas être définie pour l'énumération

Un simple d'abord: enum NoZero {Number = 1}

        public bool ReturnsFalse()
        {
            //The default value is not defined!
            return Enum.IsDefined(typeof (NoZero), default(NoZero));
        }

Le code ci-dessous peut réellement imprimer vrai!

 internal sealed class Strange
{
    public void Foo()
    {
        Console.WriteLine(this == null);
    }
}

Un simple élément de code client qui aboutira à un délégué void HelloDelegate (barre étrange);

public class Program
{
    [STAThread()]
    public static void Main(string[] args)
    {
        Strange bar = null;
        var hello = new DynamicMethod("ThisIsNull",
            typeof(void), new[] { typeof(Strange) },
         typeof(Strange).Module);
        ILGenerator il = hello.GetILGenerator(256);
        il.Emit(OpCodes.Ldarg_0);
        var foo = typeof(Strange).GetMethod("Foo");
        il.Emit(OpCodes.Call, foo);
        il.Emit(OpCodes.Ret);
        var print = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate));
        print(bar);
        Console.ReadLine();
    }
}

cela est vrai dans la plupart des langues tant que la méthode d'instance, lorsqu'elle est appelée, n'utilise pas l'état de l'objet. ceci n'est déréférencé que lorsque l'état de l'objet est accédé

2
Rune FS

Ce qui suit ne fonctionne pas:

if (something)
    doit();
else
    var v = 1 + 2;

Mais cela fonctionne:

if (something)
    doit();
else {
    var v = 1 + 2;
}
2
Anders Rune Jensen

Le cadrage dans c # est parfois vraiment bizarre. Me laisse te donner un exemple:

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

OleDbCommand command = SQLServer.CreateCommand();

La compilation est échouée car la commande est redéclarée? Il y a quelques hypothèses qui expliquent pourquoi cela fonctionne de cette manière dans ce thread sur stackoverflow et dans mon blog .

2
Anders Rune Jensen

Celui-ci est assez simple mais je le trouve toujours un peu intéressant. Quelle serait la valeur de x après l'appel à Foo?

static int x = 0;

public static void Foo()
{
    try { return; }
    finally { x = 1; }
}

static void Main() { Foo(); }
2
Andrew Sevastian

Si vous avez la méthode d'extension:

public static bool? ToBoolean(this string s)
{
    bool result;

    if (bool.TryParse(s, out result))
        return result;
    else
        return null;
}

et ce code:

string nullStr = null;
var res = nullStr.ToBoolean();

Cela ne lève pas d'exception car c'est une méthode d'extension (et vraiment HelperClass.ToBoolean(null)) et non une méthode d'instance. Cela peut être déroutant.

1
Lasse Espeholt