Який найдивніший кутовий випадок, який ви бачили в C # або .NET? [зачинено]


322

Я збираю кілька кутових кейсів і тизерів мозку і завжди хотів би почути більше. Ця сторінка дійсно охоплює біти та боби мови C #, але я також знаходжу основні .NET речі цікавими. Наприклад, ось такий, якого немає на сторінці, але який мені здається неймовірним:

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

Я б очікував, що надрукувати False - адже "new" (з типом посилання) завжди створює новий об'єкт, чи не так? Характеристики як для C #, так і для CLI вказують на те, що він повинен бути. Ну, не в цьому конкретному випадку. Він друкує True, і робиться на кожній версії рамки, з якою я його тестував. (Я не пробував цього на Mono, правда, ...)

Щоб було зрозуміло, це лише приклад того, що я шукаю - я не особливо шукав обговорення / пояснення цієї дивацтва. (Це не те саме, що звичайне строкове інтернування; зокрема, інтернування інтернету зазвичай не відбувається, коли викликається конструктор.) Я дійсно просив подібної дивної поведінки.

Будь-які інші дорогоцінні камені там ховаються?


64
Тестували на Mono 2.0 rc; повертає Правда
Марк Гравелл

10
обидва рядки в кінцевому підсумку є рядком. Порожнє, і, здається, що в рамках зберігається лише одна посилання на це
Адріан Занеску

34
Це річ збереження пам’яті. Знайдіть документацію MSDN для рядка статичного методу. CLR підтримує пуловий рядок. Ось чому рядки з однаковим вмістом відображаються як посилання на одну пам'ять, тобто на об'єкт.
Джон Лейдегрен

12
@John: Рядок інтернування відбувається автоматично лише для літераторів . Тут це не так. @DanielSwe: Інтернування не потрібно для того, щоб зробити рядки незмінними. Те, що це можливо, є приємним наслідком незмінності, але нормальне інтернування тут все одно не відбувається.
Джон Скіт

Відповіді:


394

Я думаю, я показав вам це раніше, але мені подобається веселощі тут - для цього потрібна була якась налагодження! (оригінальний код був, очевидно, більш складним і тонким ...)

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

Отже, що було Т ...

Відповідь: будь-яка Nullable<T>- наприклад int?. Усі методи перекриваються, за винятком GetType (), якого не може бути; тому він передається (у коробці) об'єкту (а значить, і до нуля) для виклику object.GetType () ... який викликає null ;-p


Оновлення: сюжет згущується ... Ейенде Рахіен кинула подібний виклик у своєму блозі , але з 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?");
}

Але це можна перемогти! Використання тієї ж непрямості, яку використовують такі речі, як видалення; попередження - чисте зло :

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

При цьому на місці new()виклик переспрямовується на проксі ( MyFunnyProxyAttribute), який повертається null. Тепер іди і вмивай очі!


9
Чому не можна визначити Nullable <T> .GetType ()? Чи не повинен результат бути typeof (Nullable <T>)?
Дрю Ноакс

69
Дрю: проблема полягає в тому, що GetType () не є віртуальним, тому його не переосмислюють - це означає, що значення викликається для виклику методу. Поле стає нульовою посиланням, отже, NRE.
Джон Скіт

10
@Малював; крім того, існують спеціальні правила боксу для Nullable <T>, а це означає, що порожні Nullable <T> коробки нульові, а не поле, яке містить порожнє Nullable <T> (і нульове un-box до порожнього Nullable <T >)
Марк Гравелл

29
Дуже, дуже круто. Неохолодженим способом. ;-)
Конрад Рудольф

6
Конструктор-обмеження, 10.1.5 в специфікаціях
лангажу

216

Округлення банкірів.

Це не стільки помилка компілятора чи несправність, але, безумовно, дивний кутовий випадок ...

.Net Framework використовує схему або округлення, відоме як "Округлення банкіра".

Під час округлення банкірів 0,5 числа округляються до найближчого парного числа, так

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

Це може призвести до деяких несподіваних помилок у фінансових розрахунках, що базуються на більш відомій округленні в раунд.

Це стосується і Visual Basic.


22
Мені це теж здалося дивним. Тобто, принаймні, до тих пір, поки я не зібрав великий список чисел і не підрахував їх суму. Потім ви усвідомлюєте, що якщо просто округнути, ви отримаєте потенційно величезну різницю від суми не округлих чисел. Дуже погано, якщо ви робите фінансові розрахунки!
Цветомир Цонев

255
У випадку, якщо люди не знали, ви можете зробити: Math.Round (x, MidpointRounding.AwayFromZero); Щоб змінити схему округлення.
ICR

26
З Документів: Поведінка цього методу випливає із стандарту 754 IEEE, розділ 4. Цей тип округлення іноді називають округленням до найближчого або округленням банкіра. Це мінімізує помилки округлення, які виникають внаслідок послідовного округлення середнього значення в одному напрямку.
ICR

8
Цікаво, чи не тому я int(fVal + 0.5)часто так часто бачу навіть мови, які мають вбудовану функцію округлення.
Бен Бланк

32
За іронією долі, я колись працював у банку, а інші програмісти почали перебирати з цього приводу, думаючи, що округлення було порушено в рамках
дан

176

Що буде виконувати ця функція, якщо викликати як Rec(0)(не під налагоджувачем)?

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

Відповідь:

  • У 32-розрядному JIT це повинно призвести до StackOverflowException
  • На 64-розрядному JIT він повинен надрукувати всі числа до int.MaxValue

Це пояснюється тим, що 64-розрядний компілятор JIT застосовує оптимізацію хвостових викликів , тоді як 32-розрядний JIT цього не робить.

На жаль, у мене немає 64-бітної машини для підтвердження цього, але метод відповідає всім умовам оптимізації хвостових викликів. Якщо у когось є такий, мені було б цікаво подивитися, чи це правда.


10
Складається у режимі випуску, але, безумовно, працює на x64 =)
Ніл Вільямс

3
можливо, варто оновити свою відповідь, коли VS 2010 вийде, оскільки всі поточні JIT потім виконають TCO у режимі випуску
ShuggyCoUk

3
Щойно приміряв VS2010 Beta 1 на 32-розрядному WinXP. Все-таки отримати StackOverflowException.
шквал

130
+1 для StackOverflowException
calvinlough

7
Це ++там мене зовсім скинуло. Ви не можете подзвонити Rec(i + 1)як звичайна людина?
конфігуратор

111

Призначте це!


Це те, що я люблю просити на вечірках (імовірно, тому мене більше не запрошують):

Чи можете ви скласти наступний фрагмент коду?

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

Легкий обман може бути:

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

Але справжнє рішення таке:

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

Тож мало відомо факт, що типи цінностей (структури) можуть перепризначити свою thisзмінну.


3
Класи C ++ теж можуть це зробити ... як я нещодавно виявив, лише кричати на те, що насправді намагаються використовувати його для оптимізації: p
mpen

1
Я фактично використовував нове місце. Просто хотів ефективного способу оновлення всіх полів :)
10.10

70
Це також накрутка: //this = new Teaser();:-)
AndrewJacksonZA

17
:-) Я вважаю за краще ці чити в моєму виробничому коді, ніж ця перекваліфікаційна гидота ...
Omer Mor

2
Від CLR через C #: Причина, яку вони зробили, полягає в тому, що ви можете викликати безпараметричний конструктор структури в іншому конструкторі. Якщо ви хочете ініціалізувати лише одне значення структури та хочете, щоб інші значення дорівнювали нулю / нулю (за замовчуванням), ви можете написати public Foo(int bar){this = new Foo(); specialVar = bar;}. Це неефективно і не дуже виправдано ( specialVarпризначається двічі), а просто БЮР. (Ось причина, наведена в книзі, я не знаю, чому ми не повинні просто так робити public Foo(int bar) : this())
kizzx2

100

Кілька років тому, працюючи над програмою лояльності, у нас виникло питання щодо кількості балів, наданих клієнтам. Проблема стосувалася кастингу / перетворення подвійних в int.

У коді нижче:

double d = 13.6;

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

i1 == i2 ?

Виходить, що i1! = I2. Через різні політики округлення в операторі «Перетворити і відкинути» фактичні значення:

i1 == 14
i2 == 13

Завжди краще зателефонувати на Math.Ceiling () або Math.Floor () (або Math.Round з MidpointRounding, що відповідає нашим вимогам)

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

44
Передача на ціле число не округляється, він просто рубає його (ефективно завжди округлюючи вниз). Тож це має повний сенс.
Макс Шмелінг

57
@Max: так, але навіщо Convert Round?
Стефан Штайнгер

18
@Stefan Steinegger Якби все, що було зроблено, було б в першу чергу, не так? Також зауважте, що назва класу - Перетворити не Cast.
помилка-лот

3
У VB: CInt () турів. Виправити () скорочення. Спалили мене один раз ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Майкл Харен

74

Вони мали б зробити 0 цілим числом, навіть якщо є перевантаження функції перерахунку.

Я знав обґрунтування основної команди C # для відображення 0 на перерахунок, але все-таки це не настільки ортогонально, як має бути. Приклад з Npgsql .

Приклад тесту:

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
Нічого собі, це для мене нове. Крім того, буде зрозуміти, як працює ConverTo.ToIn32 (), але кастинг до (int) 0 не робить. І будь-яке інше число> 0 працює. (Під "роботами" я маю на увазі викликати перевантаження об'єкта.)
Лукас,

Існує рекомендоване правило аналізу коду для застосування належних практик щодо такої поведінки: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Це посилання також містить хороший опис того, як працює 0-картографування .
Кріс Кларк

1
@Chris Clark: Я намагався поставити None = 0 на enum Symbol. все-таки компілятор вибирає enum для 0 і навіть (int) 0
Майкл Буен

2
ІМО вони повинні були ввести ключове слово, noneяке можна використовувати, перетворене на будь-який перелік, і зробило 0 завжди int, а не неявно конвертоване в enum.
CodesInChaos

5
ConverTo.ToIn32 () працює, тому що його результат - не константа компіляції. І тільки константа компіляції 0 перетворюється на enum. У більш ранніх версіях .net навіть лише літерал 0повинен був бути перетворений на перерахунок. Дивіться блог Еріка Ліпперта
CodesInChaos

67

Це одна з найбільш незвичайних, яку я бачив досі (окрім тих, що тут, звичайно!):

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

Це дозволяє вам заявити про це, але не має реального використання, оскільки воно завжди буде просити вас обернути будь-який клас, який ви наповнюєте в центрі, з іншою Черепашкою.

[жарт] Я думаю, що це черепахи аж донизу ... [/ жарт]


34
Однак ви можете створювати екземпляри:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
Справді. Це той малюнок, який переживає Java з великим ефектом. Я також його використовую в буферах протоколів.
Джон Скіт

6
RCIX, о так, так.
Джошуа

8
Я досить часто використовував цей зразок у модних дженериках. Це дозволяє такі речі, як правильно введений клон, або створення примірників себе.
Lucero

20
Це "цікаво повторюваний шаблон шаблону" en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

Ось я лише нещодавно дізнався про це ...

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

Вищезазначене на перший погляд виглядає божевільним, але насправді є законним. Ні, справді (хоча я пропустив ключову частину, але це не щось хитре, як "додати клас під назвою IFoo" або "додати usingпсевдонім, щоб вказати IFooна клас ").

Подивіться, чи можете ви зрозуміти, чому тоді: Хто каже, що ви не можете створити інтерфейс?


1
+1 для « з допомогою псевдоніма» - я ніколи не знав , що ви могли б зробити що !
Девід

хак в компіляторі для COM Interop :-)
Іон Тодірел

Сволота! Ви могли хоч би сказати "за певних обставин" ... Мій упорядник спростує!
М. А. Ханін

56

Коли булевий неправдивий і неправдивий?

Білл виявив, що ви можете зламати булеві, так що якщо A - це правда, а B - це правда, (A і B) є хибним.

Зламані булеви


134
Коли це FILE_NOT_FOUND, звичайно!
Грег

12
Це цікаво тим, що математично означає, що жодне твердження в C # не є доказовим. Ооопс.
Саймон Джонсон

20
Колись я напишу програму, яка залежить від такої поведінки, і демони найтемнішого пекла підготують для мене привітання. Bwahahahahaha!
Джеффрі Л Уітлідж

18
У цьому прикладі використовуються побітові, а не логічні оператори. Як це дивно?
Джош Лі

6
Ну, він зламає макет структури, звичайно, ви отримаєте дивні результати, це не дивно чи несподівано!
Іон Тодірел

47

Я приїжджаю трохи пізно на вечірку, але в мене є три чотири п'ять:

  1. Якщо ви опитуєте InvokeRequired на контрольному елементі, який не завантажений / показаний, він скаже помилкове - і вибухне вам в обличчя, якщо ви спробуєте змінити його з іншого потоку ( рішення - посилатися на це. контроль).

  2. Ще одне, що мене наштовхнуло, - це те, що дали збірку з:

    enum MyEnum
    {
        Red,
        Blue,
    }

    якщо ви обчислюєте MyEnum.Red.ToString () в іншій збірці, а між ними хтось перекомпілює ваш перелік:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    під час виконання, ви отримаєте "Чорний".

  3. У мене було спільне зібрання з деякими зручними константами. Мій попередник залишив безліч некрасивих властивостей для отримання лише я, я думав, що я позбудуся брязкальця і ​​просто використовувати публічну константність. Я був більш ніж трохи здивований, коли В. С. склав їх за своїми значеннями, а не посиланнями.

  4. Якщо ви реалізуєте новий метод інтерфейсу з іншої збірки, але ви відновлюєте посилання на стару версію цієї збірки, ви отримуєте TypeLoadException (не реалізація 'NewMethod'), навіть якщо ви його реалізували (див. Тут ).

  5. Словник <,>: "Порядок повернення елементів не визначений". Це жахливо , тому що іноді може вкусити тебе, але працювати іншим, і якщо ти просто сліпо припустив, що Словник буде грати добре ("чому не варто? Я думав, Ліст робить"), ти справді повинен покладіть ніс у нього, перш ніж ви нарешті почнете ставити під сумнів своє припущення.


6
№2 - цікавий приклад. Енуми - це відображення компілятора на цілісні значення. Тож навіть якщо ви явно не призначили їм значення, компілятор це зробив, в результаті чого з'явилися MyEnum.Red = 0 і MyEnum.Blue = 1. Коли ви додали Чорний, ви перезначили значення 0 для відображення з червоного на чорне. Я підозрюю, що проблема могла б проявитися і в інших звичаях, таких як серіалізація.
Л.Бушкін

3
+1 потрібно для виклику. У нашому випадку ми вважаємо за краще чітко присвоювати значення перелікам, як Red = 1, Blue = 2, тому нові можна вставити до або після того, як це завжди призведе до однакового значення. Це особливо необхідно, якщо ви зберігаєте значення в базах даних.
TheVillageIdiot

53
Я не погоджуюся з тим, що №5 - це "крайній випадок". Словник не повинен мати певного порядку, заснованого на вставленні значень. Якщо ви хочете певного порядку, скористайтеся списком або використовуйте ключ, який можна сортувати таким чином, який вам корисний, або використовуйте зовсім іншу структуру даних.
Клин

21
@Wedge, як, наприклад, SortedDictionary?
Аллон Гуралнек

4
# 3 трапляється тому, що константи вставляються як літерали скрізь, де вони використовуються (як мінімум у C #). Ваш попередник, можливо, вже помітив це, тому вони використовували властивість get-only. Однак змінна, що читається тільки (на відміну від const) буде працювати так само добре.
Рекомендація

33

VB.NET, нульові і термінальний оператор:

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

Це зайняло у мене деякий час, щоб налагодити, оскільки я очікував, що iвони містять Nothing.

Що я насправді містить? 0.

Це дивно, але насправді "правильна" поведінка: NothingVB.NET не зовсім такий, як nullу CLR: Nothingможе означати nullабо default(T)для типу значення T, залежно від контексту. У наведеному вище випадку Ifвисновок Integerяк загальний тип Nothingі 5, отже, в даному випадку Nothingозначає 0.


Досить цікаво, що мені не вдалося знайти цю відповідь, тому мені довелося створити питання . Ну, хто знав, що відповідь є у цій темі?
GSerg

28

Я знайшов другий справді дивний кутовий корпус, який б’є мій перший дальнім пострілом.

Метод String.Equals (String, String, StringComppare) насправді не є побічним ефектом.

Я працював над блоком коду, який мав це в рядку сам по собі вгорі деякої функції:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

Видалення цього рядка призводить до переповнення стека деінде в програмі.

У коді виявилося встановлення обробника для того, що по суті було подією BeforeAssemblyLoad і намагається зробити

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

Поки що я не повинен був вам говорити. Використання культури, яка раніше не використовувалася в порівнянні рядків, призводить до навантаження на збірку. InvariantCulture не є винятком із цього.


Я думаю, що "завантаження збірки" є побічним ефектом, оскільки ви можете спостерігати за цим програмою BeforeAssemblyLoad!
Джейкоб Кралл

2
Ого. Це ідеальний постріл у ногу обслуговуючого персоналу. Напевно, написання обробника до BeforeAssemblyLoad може призвести до безлічі таких сюрпризів.
перука

20

Ось приклад того, як можна створити структуру, яка спричиняє повідомлення про помилку "Спроба читати чи записувати захищену пам'ять. Це часто є свідченням пошкодження іншої пам'яті". Різниця між успіхом і невдачею дуже тонка.

Наступний тест блоку демонструє проблему.

Подивіться, чи можете ви зрозуміти, що пішло не так.

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

У мене болить голова ... Чому це не працює?
Jasonh

2
Гм, я писав це кілька місяців тому, але не можу пригадати, чому саме це сталося.
cbp

10
Схоже на помилку компілятора; ці += 500виклики: ldc.i4 500(500 штовхає як Int32), а потім call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- так це те лікує в якості decimal(96-біт) без будь - яких перетворення. Якщо ви користуєтеся, += 500Mто це правильно. Це просто схоже на те, що компілятор думає, що він може це зробити одним способом (імовірно, через неявний оператор int), а потім вирішує зробити це іншим способом.
Марк Гравелл

1
Вибачте за подвійний пост, ось більш кваліфіковане пояснення. Я додам це, мене це трохи покусало, і це гасить, хоча я розумію, чому це відбувається. Для мене це прикро обмеження структури / вартієтипу. bytes.com/topic/net/answers/…
Беннетт Кріп

2
@Ben помилки компілятора або модифікація, що не впливає на оригінальну структуру, це добре. Порушення доступу - це зовсім інший звір. Виконання не повинно ніколи його кидати, якщо ви просто пишете безпечний чистий керований код.
CodesInChaos

18

C # підтримує перетворення між масивами та списками до тих пір, поки масиви не є багатовимірними та існує відношення успадковування між типами та типами - еталонними типами

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

Зауважте, що це не працює:

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
Приклад IList <T> - це лише перелік, тому що рядок [] вже реалізує ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> та IEnumerable <string>.
Лукас

15

Це найдивніше, з чим я стикався випадково:

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

Використовується наступним чином:

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

Викине а NullReferenceException. Виявляється, численні доповнення компілюються компілятором C # на виклик String.Concat(object[]). Перед .NET 4 є помилка в тій самій перевантаженні Concat, де об'єкт перевіряється на null, але не є результатом 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;

Це помилка ECMA-334 §14.7.4:

Оператор бінарного + виконує конкатенацію рядків, коли один або обидва операнди мають тип string. Якщо є операнд конкатенації рядків null, порожня рядок підміняється. В іншому випадку будь-який нерядковий операнд перетворюється на його рядкове представлення шляхом виклику віртуального ToStringметоду, успадкованого від типу object. Якщо ToStringповертається null, підміняється порожня рядок.


3
Хм, але я можу уявити цю помилку як .ToStringсправді ніколи не повинна повертати нуль, а рядок. Порожнє. Тим не менш і помилки в рамках.
Дікам

12

Цікаво - коли я вперше подивився на те, що припустив, що компілятор C # перевіряє, але навіть якщо ви випромінюєте ІЛ безпосередньо, щоб усунути будь-який шанс втручання, це все-таки трапляється, це означає, що це дійсно newobjоп-код, який робить перевірка.

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

Він також прирівнюється до того, trueякщо ви перевіряєте, з string.Emptyякими засобами цей оп-код повинен мати особливу поведінку для інтернування порожніх рядків.


не бути розумним придурком чи що-небудь, але ви чули про рефлектор ? це досить зручно в таких випадках;
RCIX

3
Ти не розумний; ви пропускаєте суть - я хотів створити конкретний ІР для цього одного випадку. І все одно, враховуючи, що Reflection.Emit є тривіальним для цього типу сценарію, це, мабуть, так само швидко, як написання програми в C #, потім відкриття рефлектора, пошук двійкового, пошук методу тощо ... І мені навіть не потрібно залиште IDE, щоб це зробити.
Грег Бук

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

Вихід "Спроба прочитати захищену пам'ять. Це вказує на те, що інша пам'ять пошкоджена."


1
Цікаво! Схоже, помилка компілятора; Я переніс на C #, і він працює чудово. Однак, існує багато проблем із винятками, викинутими на Load, і він веде себе по-різному з / без налагоджувача - ви можете зловити з налагоджувачем, але не без (у деяких випадках).
Марк Гравелл

Вибачте, я забув, вам потрібно додати комбіновану скриньку до форми, перш ніж вона буде.
Джошуа

Це пов’язано з ініціалізацією діалогу з використанням SEH як якогось жахливого внутрішнього механізму комунікації? Я неясно пам'ятаю щось подібне в Win32.
Daniel Earwicker

1
Це та сама проблема cbp вище. Повертається вартієтип - це копія, тому будь-які посилання на будь-які властивості, що випливають із зазначеної копії, спрямовуються на землю біт-ковша ... bytes.com/topic/net/answers/…
Bennett Dill

1
Ні. Тут немає ніяких структур. Я фактично налагодив це. Він додає NULL до колекції елементів списку нативної комбінованої коробки, спричиняючи затримку збою.
Джошуа

10

PropertyInfo.SetValue () може присвоювати ints enums, ints nullable ints, enums to nulible enums, але не ints to nulible enums.

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

Повний опис тут


10

Що робити, якщо у вас є загальний клас, який має методи, які можна зробити неоднозначними залежно від аргументів типу? Нещодавно я зіткнувся з цією ситуацією, написавши двосторонній словник. Я хотів написати симетричні Get()методи, які б повернули протилежне тому, що б аргумент був переданий. Щось на зразок цього:

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

Все добре, якщо ви робите примірник, де T1і T2є різні типи:

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

Але якщо T1і T2однакові (і, ймовірно, якщо один був підкласом іншого), це помилка компілятора:

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

Цікаво, що всі інші методи у другому випадку все ще застосовні; це лише виклики до тепер неоднозначного методу, який викликає помилку компілятора. Цікавий випадок, якщо трохи малоймовірний і незрозумілий.


Противники перевантаження методу сподобаються цьому ^^.
Крістіан Клаузер

1
Я не знаю, це має для мене тотальний сенс.
Скотт Вітлок

10

C # Доступність Puzzler


Наступний похідний клас отримує доступ до приватного поля зі свого базового класу, і компілятор мовчки дивиться в іншу сторону:

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

Поле справді приватне:

private int m_basePrivateField = 0;

Хочете вгадати, як ми можемо зробити такий компіляцію коду?

.

.

.

.

.

.

.

Відповідь


Хитрість полягає в тому, щоб оголосити Derivedяк внутрішній клас Base:

public class Base
{
    private int m_basePrivateField = 0;

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

Внутрішні класи отримують повний доступ до зовнішніх членів класу. У цьому випадку внутрішній клас також трапляється із зовнішнього класу. Це дозволяє нам "порушити" інкапсуляцію приватних членів.


Це насправді добре задокументовано; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Це може бути корисною особливістю, особливо якщо зовнішній клас є статичним.

Так - звичайно, це документально підтверджено. Однак дуже мало людей вирішили цю головоломку, тому я подумав, що це класна дрібниця.
Омер Мор

2
Здається, у вас є дуже велика можливість переповнення стека, якщо внутрішній клас успадкує його власника ...
Джеймі Треворгі

Ще один подібний (і цілком правильний) випадок полягає в тому, що об’єкт може отримати доступ до приватного члена іншого об'єкта того ж типу:class A { private int _i; public void foo(A other) { int res = other._i; } }
Олів'є Якот-Декомбс

10

Щойно знайшов приємну дрібницю сьогодні:

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

Це кидає помилку компіляції.

Виклик методу "Ініціалізувати" потрібно динамічно відправляти, але не може бути, тому що він є частиною виразу базового доступу. Розгляньте можливість введення динамічних аргументів або усунення базового доступу.

Якщо я напишу base.Initialize (речі як об'єкт); вона працює чудово, однак це здається "магічним словом" тут, оскільки вона робить саме так, все одно сприймається як динамічне ...


8

В API, який ми використовуємо, методи, які повертають об’єкт домену, можуть повернути спеціальний "нульовий об'єкт". При здійсненні цього оператор порівняння і Equals()метод переосмислюються для повернення, trueякщо його порівнювати з null.

Отже, користувач цього API може мати такий код, як цей:

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

або, можливо, трохи більш багатослівний, як це:

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

де GetDefault()метод, який повертає якесь значення за замовчуванням, яке ми хочемо використовувати замість null. Несподіванка вразила мене, коли я використовував ReSharper і дотримуючись його рекомендації, щоб переписати будь-яке з них на наступне:

return test ?? GetDefault();

Якщо тестовим об'єктом є нульовий об’єкт, повернутий з API замість належного null, поведінка коду тепер змінилася, оскільки оператор зведення нуля фактично перевіряє null, чи не працює operator=або Equals().


1
насправді не вир # кутовий випадок, але шановний пане, що це придумав?!?
Рей Буйсен

Це не лише код, що використовує нульові типи? Тому ReSharper рекомендує "??" використання. Як сказав Рей, я б не думав, що це кутовий випадок; чи я помиляюся?
Тоні

1
Так, типи є нульовими - і додатково є NullObject. Якщо це кутовий випадок, я не знаю, але принаймні це випадок, коли 'if (a! = Null) повертає a; повернути b; ' це не те саме, що 'return a ?? б '. Я абсолютно згоден, що проблема з дизайном Framework / API - перевантаження == null для повернення істини на об'єкт, звичайно, не дуже гарна ідея!
Tor Livar

8

Розглянемо цей дивний випадок:

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

Якщо Baseі Derivedбуде оголошено в одній збірці, компілятор зробить Base::Methodвіртуальні та запечатані (в CIL), навіть Baseне інтерфейс реалізований.

Якщо Baseі Derivedзнаходяться в різних асамблеях, під час компіляції Derivedзбірки компілятор не змінить іншу збірку, тому він введе члена в Derivedтому, що буде явною реалізацією, для MyInterface::Methodчого просто делегуватиме виклик Base::Method.

Компілятор повинен зробити це для того, щоб підтримувати поліморфну ​​розсилку щодо інтерфейсу, тобто він повинен зробити цей метод віртуальним.


Це справді звучить дивно. Дослідити доведеться пізніше :)
Джон Скіт

@ Джон тарілочках: Я знайшов це в той час як дослідження стратегій реалізації для ролей в C # . Було б здорово отримати відгук про це!
Йордао

7

Наступне може бути загальним знанням, якого мені просто бракувало, але так. Деякий час тому у нас був випадок помилки, який включав віртуальні властивості. Трохи абстрагуючи контекст, врахуйте наступний код і застосуйте точку розриву до вказаної області:

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

Перебуваючи в Derivedконтексті об'єкта, ви можете отримувати таку саму поведінку під час додавання base.Propertyгодинника або введення base.Propertyв швидкий годинник.

Знадобилося трохи часу, щоб зрозуміти, що відбувається. Врешті-решт мене просвітив Швидкий годинник. Під час заходу в Quickwatch та вивчення Derivedоб'єкта d (або з контексту об'єкта this) та вибору поля base, поле редагування у верхній частині Quickwatch відображає такий склад:

((TestProject1.Base)(d))

Що означає, що якщо база буде замінена як така, то дзвінок був би

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

для інструментів "Годинники", "Швидкий годинник" та налагодження миші за налагодженням, і тоді це має сенс відображати "AWESOME"замість того, "BASE_AWESOME"щоб розглянути питання про поліморфізм. Я все ще не впевнений, чому це перетворило б його в склад, одна з гіпотез - це callможе бути недоступно лише з контексту цих модулів callvirt.

Так чи інакше, це, очевидно, нічого не змінює з точки зору функціональності, Derived.BasePropertyвсе одно дійсно повернеться "BASE_AWESOME", і, таким чином, це не було коренем нашої помилки на роботі, просто заплутаною складовою. Однак мені було цікаво, як це може ввести в оману розробників, які не знають про цей факт під час сеансів налагодження, особливо якщо Baseце не викрито у вашому проекті, а посилається на сторонні DLL, в результаті чого Devs просто сказав:

"Ой, зачекайте. Що? Омг, схожий на DLL, .. роблячи щось смішне"


У цьому немає нічого особливого, це лише спосіб відміни роботи.
конфігуратор

7

Це досить важко вдосконалити. Я наткнувся на це, коли я намагався створити реалізацію RealProxy, яка справді підтримує Begin / EndInvoke (дякую MS, що зробили це неможливо без жахливих хак). Цей приклад в основному є помилкою в CLR, шлях некерованого коду для BeginInvoke не підтверджує, що повідомлення, що повертається з RealProxy.PrivateInvoke (і мій Invoke override) повертає екземпляр IAsyncResult. Після повернення CLR неймовірно плутається і втрачає будь-яке уявлення про те, що відбувається, як показали тести внизу.

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

Вихід:

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

Я не впевнений, чи не скажете ви, що це дивацтва з Windows Vista / 7 чи диваком .Net, але це мені на деякий час чухало голову.

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

У Windows Vista / 7 файл буде фактично записаний C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
Це дійсно підвищення безпеки (не 7, afaik). Але прикольна річ у тому, що ви можете читати та відкривати файл із шлях до програмних файлів, тоді як якщо ви заглянете туди з Explorer, нічого не буде. Цей зайняв у мене майже день роботи @ клієнта, перш ніж я нарешті це дізнався.
Анрі

Це, безумовно, і Windows 7. Ось чим я користувався, коли наткнувся на нього. Я розумію міркування, які випливають з цього питання, але все-таки було важко з'ясувати.
Спенсер Рупорт

У Vista / Win 7 (також технічно winXP) програми повинні записувати в папку AppData в папці "Користувачі", як технічно дані користувачів. Програми не повинні писати програмові файли / windows / system32 / тощо ніколи, якщо вони не мають адміністративних привілеїв, і ці привілеї повинні бути тільки там, щоб сказати, що оновити програму / видалити її / встановити нову функцію. АЛЕ! Все-таки не пишіть до system32 / windows / тощо :) Якщо ви запустили цей код вище як адміністратор (клацніть правою кнопкою миші> запустити як адміністратор), він теоретично повинен записувати в папку програмних файлів програм.
Стів Сифухс


6

Ви ніколи не думали, що компілятор C # може генерувати недійсний CIL? Запустіть це, і ви отримаєте 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));
  }
}

Я не знаю, як це коштує в компіляторі C # 4.0.

EDIT : це вихід з моєї системи:

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]

Для мене працює як компілятор C # 3.5, так і компілятор C # 4 ...
Джон Скіт

У моїй системі вона не працює. Я вставлю висновок у запитання.
Йордао

Не вдалося мені в .NET 3.5 (не встигаю тестувати 4.0). І я можу повторити проблему з кодом VB.NET.
Марк Херд

3

У C # є щось дійсно захоплююче, як він справляється із закриттями.

Замість того, щоб копіювати значення змінних стеків до змінної, яка не закривається, вона робить магію препроцесора, що обертає всі випадки змінної в об'єкт і, таким чином, переміщує її зі стека - прямо до купи! :)

Я думаю, це робить C # навіть більш функціонально повним (або лямбда-повним так)) мовою, ніж сам ML (який використовує копіювання значення стека AFAIK). F # має і цю особливість, як це робить C #.

Це приносить мені велике задоволення, дякую хлопці MS!

Це не дивний або кутовий випадок ... але щось дійсно несподіване з мови VM на основі стека :)


3

З питання, яке я недавно задав:

Умовний оператор не може подавати неявно?

Подано:

Bool aBoolValue;

Де aBoolValueпризначено або Істинне, або Неправильне;

Наступне не компілюється:

Byte aByteValue = aBoolValue ? 1 : 0;

Але це:

Int anIntValue = aBoolValue ? 1 : 0;

Надана відповідь теж досить хороша.


хоча я ve not test it Iвпевнений, що це спрацює: Байт aByteValue = aBoolValue? (Байт) 1: (Байт) 0; Або: байт aByteValue = (байт) (aBoolValue? 1: 0);
Олексій Пачурар

2
Так, Алекс, це спрацювало б. Ключ полягає в неявному кастингу. 1 : 0поодинці буде неявно приведено до int, а не до Byte.
MPelletier

2

Оцінка рівня в c # часом справді химерна. Дозвольте навести один приклад:

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

OleDbCommand command = SQLServer.CreateCommand();

Це не вдалося компілювати, тому що команда передекларована? Є кілька зацікавлених здогадок щодо того, чому він працює таким чином у цій темі на stackoverflow та в моєму блозі .


34
Я не вважаю це особливо химерним. Те, що ви називаєте "абсолютно правильним кодом" у своєму блозі, є абсолютно невірним відповідно до мовної специфікації. Це може бути правильним у якійсь уявній мові, якою ви хотіли б бути C #, але специфікація мови цілком зрозуміла, що в C # вона недійсна.
Джон Скіт

7
Добре це дійсно в C / C ++. А оскільки це C #, я б хотів, щоб він все ще працював. Що мене найбільше клопотить - це те, що компілятор не має цього робити. Це не так, як важко робити вкладені масштаби. Я думаю, все це зводиться до стихії найменшого сюрпризу. Це означає, що може бути те, що специфікація каже це і те, але це насправді не дуже допомагає мені, якщо це абсолютно нелогічно, що він так поводиться.
Anders Rune Jensen

6
C #! = C / C ++. Ви також хочете використовувати cout << "Привіт, світ!" << endl; замість Console.WriteLine ("Привіт, світ!") ;? Також це нелогічно, просто прочитайте специфікацію.
Креднс

9
Я говорю про правила визначення обсягу, що є частиною основної мови. Ви говорите про стандартну бібліотеку. Але зараз мені зрозуміло, що я повинен просто прочитати крихітну специфікацію мови c #, перш ніж розпочати програмування на ній.
Anders Rune Jensen

6
Ерік Ліпперт фактично опублікував причини, чому C # створений так недавно: blogs.msdn.com/ericlippert/archive/2009/11/02/… . Підсумок є тому, що менше ймовірність, що зміни матимуть ненавмисні наслідки.
Гелефант
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.