Какой самый странный случай из всех, что вы видели в 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, и сделал это на каждой версии фреймворка, с которым я его тестировал. (Я не пробовал это на Моно, по общему признанию ...)

Просто чтобы прояснить, это всего лишь пример того, что я ищу - я не особенно искал обсуждения / объяснения этой странности. (Это не то же самое, что обычное интернирование строк; в частности, интернирование строк обычно не происходит при вызове конструктора.) Я действительно просил подобное странное поведение.

Есть еще какие-нибудь драгоценные камни?

Джон Скит
источник
64
Проверено на Mono 2.0 RC; возвращает True
Марк Гравелл
10
обе строки заканчиваются тем, что становятся string.Empty, и кажется, что фреймворк содержит только одну ссылку на это
Adrian Zanescu
34
Это вещь для сохранения памяти. Посмотрите документацию MSDN для статического метода string.Intern. CLR поддерживает пул строк. Вот почему строки с одинаковым содержимым отображаются как ссылки на одну и ту же память, т.е. на объект.
Джон Лейдгрен
12
@John: интернирование строк происходит автоматически только для литералов . Это не тот случай, здесь. @DanielSwe: Interning не требуется для того, чтобы сделать строки неизменяемыми. Тот факт, что это возможно, является хорошим следствием неизменности, но нормального интернирования здесь не происходит в любом случае.
Джон Скит
3
Подробности реализации, вызывающие такое поведение, объясняются здесь: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Лиран,

Ответы:

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

Так, что было T ...

Ответ: любой Nullable<T>- такой как int?. Все методы переопределены, кроме GetType (), который не может быть; поэтому он приводится (в штучной упаковке) к объекту (и, следовательно, к нулю) для вызова object.GetType () ... который вызывает null ;-p


Обновление: сюжет утолщается ... Ayende Rahien бросил аналогичный вызов в своем блоге , но с 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> устанавливает значение null, а не блок, который содержит пустой Nullable <T> (и нулевой un-box для пустого Nullable <T> >)
Марк Гравелл
29
Очень, очень круто. Нехорошим способом. ;-)
Конрад Рудольф
6
Ограничение конструктора, 10.1.5 в спецификации языка C # 3.0
Марк Гравелл
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
Из документов: Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот тип округления иногда называют округлением до ближайшего или округлением банкира. Это минимизирует ошибки округления, возникающие в результате последовательного округления значения средней точки в одном направлении.
ICR
8
Интересно, вот почему я int(fVal + 0.5)так часто вижу даже в языках, которые имеют встроенную функцию округления.
Бен Бланк
32
По иронии судьбы, я однажды работал в банке, и другие программисты начали срываться по этому поводу, думая, что в рамках было нарушено округление
Dan
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 в режиме Release
ShuggyCoUk
3
Только что попробовал на VS2010 Beta 1 на 32-битном WinXP. Все еще получите исключение StackOverflowException.
squillman
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
Я на самом деле использовал новое место. Просто хотел эффективный способ обновить все поля :)
mpen
70
Это тоже чит: //this = new Teaser();:-)
AndrewJacksonZA
17
:-) Я бы предпочел эти читы в моем производственном коде, чем эту мерзость переназначения ...
Омер Мор
2
Из CLR через C #: причина, по которой они сделали это, заключается в том, что вы можете вызвать безпараметрический конструктор структуры в другом конструкторе. Если вы хотите инициализировать только одно значение структуры и хотите, чтобы другие значения были равны нулю / нулю (по умолчанию), вы можете написать public Foo(int bar){this = new Foo(); specialVar = bar;}. Это неэффективно и не совсем оправдано ( specialVarназначается дважды), но просто к сведению. (Это причина, приведенная в книге, я не знаю, почему мы не должны просто делать это public Foo(int bar) : this())
kizzx2
100

Несколько лет назад, когда мы работали над программой лояльности, у нас возникла проблема с количеством баллов, начисляемых клиентам. Проблема была связана с приведением / преобразованием double в int.

В коде ниже:

double d = 13.6;

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

i1 == i2 ?

Оказывается, что i1! = I2. Из-за различных политик округления в операторе Convert и cast действительные значения:

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
@ Макс: да, но почему Конвертировать раунд?
Стефан Штайнеггер
18
@ Stefan Steinegger Если бы все, что он сделал, было разыграно, не было бы никакой причины для этого, не так ли? Также обратите внимание, что имя класса Convert not Cast.
ошибок
3
В раундах VB: CInt (). Исправить () усечения. Сожгли меня однажды ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Майкл Харен,
74

Они должны были сделать 0 целым числом, даже если перегружена функция enum.

Я знал обоснование команды ядра C # для отображения 0 в enum, но все же оно не так ортогонально, как должно быть. Пример из 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
Michael Buen
2
IMO, они должны были ввести ключевое слово, noneкоторое можно использовать для преобразования в любое перечисление, и сделать 0 всегда int и неявно конвертируемым в перечисление.
CodesInChaos
5
ConverTo.ToIn32 () работает, потому что его результат не является константой времени компиляции. И только константа времени компиляции 0 может быть преобразована в перечисление. В более ранних версиях .net даже только литерал 0должен был быть преобразован в enum. См. Блог Эрика Липперта: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos
67

Это одна из самых необычных вещей, которые я когда-либо видел (кроме тех, что здесь, конечно!):

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

Он позволяет вам объявить это, но не имеет реального использования, так как он всегда будет просить вас обернуть любой класс, который вы наберете в центре, другой черепахой.

[шутка] Я предполагаю, что это черепахи полностью вниз ... [/ шутка]

RCIX
источник
34
Однако вы можете создавать экземпляры:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Марк Гравелл
24
На самом деле. Это шаблон, который перечисления Java используют с большим эффектом. Я тоже использую это в Protocol Buffers.
Джон Скит
6
RCIX, о да, это так.
Джошуа
8
Я довольно часто использовал этот шаблон в модных дженериках. Это позволяет такие вещи, как правильно типизированный клон или создание экземпляров самого себя.
Лусеро
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 :-)
Ion Todirel
Сволочь! Вы могли бы по крайней мере сказать "при определенных обстоятельствах" ... Мой компилятор опровергает!
М. А. Ханин
56

Когда логическое значение не является ни истинным, ни ложным?

Билл обнаружил, что вы можете взломать логическое значение, так что если A - это True, а B - True, (A и B) - False.

Взломанные логические выражения

Джонатан Аллен
источник
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. У меня была общая сборка с несколькими удобными константами. Мой предшественник оставил массу уродливых свойств, доступных только для get, я думал, что избавлюсь от беспорядка и просто использую public const. Я был более чем удивлен, когда VS скомпилировал их с их значениями, а не ссылками.

  4. Если вы реализуете новый метод интерфейса из другой сборки, но перестраиваете, ссылаясь на старую версию этой сборки, вы получаете исключение TypeLoadException (без реализации NewMethod), даже если вы его реализовали (см. Здесь ).

  5. Словарь <,>: «Порядок возврата элементов не определен». Это ужасно , потому что это может иногда кусать вас, но работать на других, и если вы просто слепо предполагали, что Словарь будет играть хорошо («почему бы и нет? Я подумал, Список делает»), вам действительно нужно держи свой нос перед тем, как наконец начнешь сомневаться в своем предположении.

Benjol
источник
6
# 2 интересный пример. Перечисления являются отображениями компилятора в целочисленные значения. Поэтому, даже если вы явно не присваивали им значения, это сделал компилятор, в результате чего MyEnum.Red = 0 и MyEnum.Blue = 1. Когда вы добавили черный, вы переопределили значение 0, чтобы отобразить с красного на черный. Я подозреваю, что проблема могла бы проявиться и в других случаях, таких как сериализация.
Л.Бушкин
3
+1 для вызова требуется. У нас мы предпочитаем явно присваивать значения перечислениям, таким как Red = 1, Blue = 2, чтобы новое можно было вставить до или после, оно всегда будет иметь одно и то же значение. Это особенно необходимо, если вы сохраняете значения в базы данных.
TheVillageIdiot
53
Я не согласен с тем, что № 5 - «крайний случай». Словарь не должен иметь определенный порядок, основанный на том, когда вы вставляете значения. Если вам нужен определенный порядок, используйте список или используйте ключ, который можно отсортировать удобным для вас способом, или используйте совершенно другую структуру данных.
Клин
21
@ Клин, как SortedDictionary возможно?
Аллон Гуралнек
4
# 3 происходит потому, что константы вставляются как литералы везде, где они используются (по крайней мере, в C #). Ваш предшественник, возможно, уже заметил это, поэтому они использовали свойство get-only. Тем не менее, переменная только для чтения (в отличие от const) будет работать так же хорошо.
Remoun
33

VB.NET, обнуляемый и троичный оператор:

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

Это заняло у меня некоторое время для отладки, так как я ожидал iсодержатьNothing .

Что я на самом деле содержит? 0,

Это удивительно, но на самом деле «правильное» поведение: Nothingв VB.NET это не совсем то же самое, что nullв CLR: Nothingможет означать nullили default(T)тип значения T, в зависимости от контекста. В приведенном выше случае Ifвыводится Integerкак общий тип Nothingи 5, значит, в данном случае, Nothingозначает 0.

Heinzi
источник
Интересно, что я не смог найти этот ответ, поэтому мне пришлось создать вопрос . Ну кто знал ответ в этой теме?
GSerg
28

Я обнаружил второй действительно странный угловой корпус, который намного превосходит мой первый.

Метод String.Equals (String, String, StringComparison) фактически не имеет побочных эффектов.

Я работал над блоком кода, который содержал это в отдельной строке вверху какой-то функции:

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);
        }
    }
CBP
источник
У меня болит голова ... Почему это не работает?
Джейсон
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
Извините за двойной пост, вот более квалифицированное объяснение. Я добавлю это, я был немного этим, и это отстой, хотя я понимаю, почему это происходит. Для меня это неудачное ограничение struct / valuetype. bytes.com/topic/net/answers/…
Беннет Дилл
2
@ Получать ошибки компилятора или модификацию, не влияющую на исходную структуру, это нормально. Нарушение доступа - это совсем другой зверь. Среда выполнения никогда не должна его выбрасывать, если вы просто пишете безопасный чистый управляемый код.
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> просто приведен, потому что string [] уже реализует 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 была ошибка, когда объект проверяется на нулевое значение, но не результат 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действительности она никогда не должна возвращать ноль, кроме строки. Пусто. Тем не менее и ошибка в рамках.
Dykam
12

Интересно - когда я впервые посмотрел на это, я предположил, что это что-то, что проверял компилятор C #, но даже если вы генерируете IL напрямую, чтобы устранить любую возможность вмешательства, это все равно происходит, что означает, что это действительно 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
Ты не умный; Вы упускаете суть - я хотел создать конкретный IL для этого случая. И в любом случае, учитывая, что 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.
Даниэль Эрвикер
1
Это та же проблема, что и CBP выше. Возвращаемый тип valuetype является копией, поэтому любые ссылки на любые свойства, вытекающие из указанной копии, направляются в область битовых массивов
Bennett Dill
1
Нет. Здесь нет структур. Я на самом деле отладил это. Он добавляет NULL в коллекцию элементов списка собственного поля со списком, вызывая задержку сбоя.
Джошуа
10

PropertyInfo.SetValue () может назначать целые числа для перечислений, целые числа для пустых чисел, перечисления для пустых перечислений, но не целые для пустых перечислений.

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

Интересно, что все остальные методы во втором случае все еще можно использовать; это только вызовы теперь неоднозначного метода, который вызывает ошибку компилятора. Интересный случай, если немного маловероятно и малоизвестно.

tclem
источник
Противники перегрузки метода будут любить этот ^^.
Кристиан Клаузер
1
Я не знаю, это имеет смысл для меня.
Скотт Уитлок
10

C # Accessibility 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);
   //...
   }
}

Это выдает ошибку компиляции.

Вызов метода Initialize должен выполняться динамически, но не может быть потому, что он является частью выражения базового доступа. Рассмотрите приведение динамических аргументов или устранение базового доступа.

Если я напишу base.Initialize (материал как объект); это работает отлично, однако это кажется "волшебным словом" здесь, поскольку оно делает то же самое, все по-прежнему воспринимается как динамическое

TDaver
источник
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
не совсем ac # угловой случай, но дорогой лорд, который придумал это?!?
Рэй Бойсен
Разве этот код не использует просто обнуляемые типы? Отсюда ReSharper рекомендует "??" использовать. Как сказал Рэй, я бы не подумал, что это угловой случай; или я не прав?
Тони
1
Да, типы могут быть обнуляемыми - кроме того, есть NullObject. Если это угловой случай, я не знаю, но, по крайней мере, это случай, когда 'if (a! = Null) return a; вернуть б; не то же самое, что «вернуть? б. Я абсолютно согласен, что это проблема с дизайном фреймворка / API - перегрузка == null для возврата true для объекта, безусловно, не очень хорошая идея!
Тор Ливар
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.

Компилятор должен сделать это для поддержки полиморфной диспетчеризации в отношении интерфейса, то есть он должен сделать этот метод виртуальным.

Jordão
источник
Это действительно звучит странно. Придется расследовать позже :)
Джон Скит
@ Джон тарелочкам: Я нашел это в то время как исследования стратегий реализации для ролей в 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. Когда вы входите в Quickwatch и исследуете Derivedобъект d (или из контекста объекта this) и выбираете поле base, поле редактирования в верхней части Quickwatch отображает следующее приведение:

((TestProject1.Base)(d))

Это означает, что если база будет заменена как таковая, вызов будет

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

для часов, Quickwatch и отладочных подсказок при наведении курсора, и тогда имело бы смысл отображать их "AWESOME"вместо "BASE_AWESOME"рассмотрения полиморфизма. Я до сих пор не уверен, почему это превратило бы его в приведение, одна гипотеза которого callможет быть недоступна из контекста этих модулей, и толькоcallvirt .

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

«Ой, подождите .. что? О боже, что это DLL, что-то смешное»

Dynami Le Savard
источник
В этом нет ничего особенного, это просто способ переопределить работу.
конфигуратор
7

Это довольно сложно. Я столкнулся с этим, когда пытался создать реализацию RealProxy, которая действительно поддерживает Begin / EndInvoke (спасибо MS за то, что это невозможно без ужасных хаков). Этот пример в основном является ошибкой в ​​CLR, неуправляемый путь к коду для BeginInvoke не проверяет, что возвращаемое сообщение от RealProxy.PrivateInvoke (и мое переопределение Invoke) возвращает экземпляр 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, афаик) повышение безопасности. Но самое интересное в том, что вы можете читать и открывать файл с путем к программным файлам, в то время как если вы посмотрите там с помощью проводника, то там ничего нет. Этот занял у меня почти рабочий день у клиента, прежде чем я наконец узнал об этом.
Анри
Это определенно вещь для Windows 7 тоже. Это то, что я использовал, когда столкнулся с этим. Я понимаю причину этого, но все равно было сложно понять.
Спенсер Рупорт
В Vista / Win 7 (технически тоже winXP) приложения должны записывать в папку AppData в папке Users папку, в качестве технически пользовательских данных. Приложения никогда не должны писать в файлы программ / windows / system32 / etc, если только у них нет привилегий администратора, и эти привилегии должны присутствовать только для того, чтобы сказать, обновить программу / удалить ее / установить новую функцию. НО! Тем не менее, не пишите в system32 / windows / etc :) Если вы запустили этот код выше как администратор (щелкните правой кнопкой мыши> запустите от имени администратора), он теоретически должен записать в папку приложения программные файлы.
Стив Сифухс
Похоже на виртуализацию - crispybit.spaces.live.com/blog/cns!1B71C2122AD43308!134.entry
Колин Ньюэлл
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, хотя.

РЕДАКТИРОВАТЬ : это вывод из моей системы:

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 на основе стека :)

Bubba88
источник
3

Из вопроса, который я задал не так давно:

Условный оператор не может быть приведен неявно?

Дано:

Bool aBoolValue;

куда aBoolValue присваивается значение True или False;

Следующее не будет компилироваться:

Byte aByteValue = aBoolValue ? 1 : 0;

Но это будет:

Int anIntValue = aBoolValue ? 1 : 0;

Ответ также довольно хорош.

оборота MPelletier
источник
хотя я ve not test it Iуверен, что это будет работать: Byte aByteValue = aBoolValue? (Байт) 1: (байт) 0; Или: Byte aByteValue = (Byte) (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 #, мне бы хотелось, чтобы он продолжал работать. Больше всего меня беспокоит то, что у компилятора нет причин делать это. Это не так сложно сделать вложенную область видимости. Я предполагаю, что все сводится к элементу наименьшего удивления. Это означает, что может быть, что спецификация говорит то и это, но это не очень мне помогает, если совершенно нелогично, что так себя ведет.
Андерс Руне Дженсен
6
C #! = C / C ++. Вы также хотели бы использовать cout << "Hello World!" << endl; вместо Console.WriteLine («Привет, мир!») ;? Также это не нелогично, просто прочитайте спецификацию.
Креднс
9
Я говорю о правилах определения объема, которые являются частью языка. Вы говорите о стандартной библиотеке. Но теперь мне ясно, что я должен просто прочитать крошечную спецификацию языка c #, прежде чем начинать программировать на нем.
Андерс Руне Дженсен
6
Эрик Липперт на самом деле недавно опубликовал причины, по которым C # разработан таким образом: blogs.msdn.com/ericlippert/archive/2009/11/02/… . Это объясняется тем, что менее вероятно, что изменения будут иметь непредвиденные последствия.
Helephant