Какой самый странный случай в C# или .NET вы видели?

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

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

Я ожидал, что напечатает False - в конце концов, «новый» (со ссылочным типом) всегда создает новый объект, не так ли? В спецификациях как для C#, так и для интерфейса командной строки указано, что это необходимо. Ну, не в этом конкретном случае. Он печатает True и работает с каждой версией фреймворка, с которой я его тестировал. (Признаюсь, я не пробовал на Mono ...)

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

Есть ли еще какие-то сокровища?

Мне нравятся эти головоломки. Я думаю, что большинство из них - просто интересные и странные крайние случаи, но тот, который касается захвата переменных анонимными методами, больше похож на нечто, что каждый должен понять.

Robert Rossney 12.10.2008 00:14

Проверено на Mono 2.0 RC; возвращает True

Marc Gravell 12.10.2008 01:35

Отредактировано, чтобы объяснить, что пример строки был всего лишь примером.

Jon Skeet 12.10.2008 22:07

я думал, что строки .net неизменяемы и уникальны по содержанию; вы проверяли object.ReferenceEquals (x, string.Empty)?

Steven A. Lowe 16.11.2008 08:34

Они неизменны, но не обязательно уникальны. Если вы измените «new char [0]» на «new char [] {'x'}» в обеих строках, вы получите ссылки на отдельные, но равные объекты.

Jon Skeet 16.11.2008 10:15

Там должна быть какая-то прикольная оптимизация ...

Doctor Jones 27.11.2008 22:06

Ага - оптимизация, которая нарушает спецификации, которые говорят, что будет выделен новый объект.

Jon Skeet 27.11.2008 22:45

обе строки в конечном итоге становятся string.Empty, и кажется, что фреймворк хранит только одну ссылку на это

Adrian Zanescu 14.01.2009 18:49

Есть где-нибудь ответ?

Fowl 02.02.2009 14:04

@Fowl: ответ на вопрос, почему конструктор строк ведет себя именно так? Нет, не совсем.

Jon Skeet 02.02.2009 15:13

Это экономия памяти. Найдите в документации MSDN строку статического метода. CLR поддерживает пул строк. Вот почему строки с одинаковым содержимым отображаются как ссылки на одну и ту же память, то есть на объект.

John Leidegren 23.02.2009 15:48

Я слышал, что это один из краеугольных камней неизменности строк.

Daniel O 25.03.2009 18:19

@John: Интернирование строк происходит автоматически только для литералы. Здесь дело обстоит не так. @DanielSwe: Интернирование - это не требуется для того, чтобы сделать строки неизменяемыми. Тот факт, что это возможно, является хорошим следствием неизменности, но нормального интернирования здесь все равно не происходит.

Jon Skeet 25.03.2009 18:27

Ну, пустая строка - это, может быть, литерал. Это в Дельфах

Marco van de Voort 04.05.2009 13:03

@Marco: "" - строковый литерал. Создание пустой строки другим способом - это не то же самое, что строковый литерал.

Jon Skeet 04.05.2009 13:25

Вы должны добавить один отсюда: groups.google.com/group/…

Marc Gravell 04.06.2009 23:48

@Downvoter: Не могли бы вы прокомментировать, почему вы проголосовали против этого?

Jon Skeet 24.07.2009 19:14

Возможно, сейчас уже немного поздно, но ... Должно быть вики сообщества.

finnw 05.02.2010 00:25

@finnw: Готово. Не уверен, почему этого не было раньше ...

Jon Skeet 05.02.2010 01:53

@opc: У меня мертвая страница по этой ссылке :(

Jon Skeet 05.02.2010 01:54

Подробности реализации, вызывающие такое поведение, объясняются здесь: blog.liranchen.com/2010/08/brain-teasing-with-strings.html

Liran 14.09.2010 22:00

Почти интересно, что люди не ожидают, что ссылка будет равной, но они ожидают, что == будет работать между двумя строками. Это связано с Intern и тем, как .Net хранит строки. Любая строка, содержащая одни и те же данные, является одним и тем же объектом.

Tedd Hansen 24.01.2011 11:03

@Tedd: Это просто неправда. Строка литералы попадает в один и тот же объект, но вы можете легко создать два разных строковых объекта с одинаковыми текстовыми данными. Они по-прежнему будут сравниваться как равные с использованием перегруженного оператора ==, пока оба выражения имеют строковый тип во время компиляции, поскольку тогда компилятор знает, что нужно вызвать перегруженный оператор.

Jon Skeet 24.01.2011 11:05

строка a = "Тест"; строка b = String.IsInterned ("Te" + "st"); Debug.WriteLine (объект.ReferenceEquals (a, b)); // Правда

Tedd Hansen 24.01.2011 11:27

@Jon - Верно, можно. Но .Net пытается сохранить одну строку только один раз - отсюда и результат. String.Intern и String.IsInterned - это методы, используемые для поиска существующих строк.

Tedd Hansen 24.01.2011 11:30

@Tedd: Я не уверен, что должен показать этот код. Мы знаем, что литералы интернированы, поэтому безымянный временный объект, переданный в IsInterned, ищет ранее интернированное значение. Однако, если бы он не был буквально или интернирован вызовом Intern, результат был бы нулевым. На практике люди редко вызывают какой-либо метод, поэтому строки, которые генерируются на лету (в отличие от литералов), сравниваются посимвольно, а не по ссылке.

Steven Sudit 24.01.2011 21:34

@Tedd: Он не «пытается» сохранить одну строку только один раз. Да, вы может вызываете Intern и IsInterned, но они редко встречаются.

Jon Skeet 24.01.2011 21:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
322
27
118 087
37
Перейти к ответу Данный вопрос помечен как решенный

Ответы 37

Ответ принят как подходящий

Думаю, я уже показывал вам это раньше, но мне нравится, как здесь весело - это потребовало некоторой отладки, чтобы отследить! (исходный код был явно более сложным и тонким ...)

    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 (), которого не может быть; поэтому он приводится (помещается в коробку) к объекту (и, следовательно, к null) для вызова 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. А теперь иди и промой глаза!

Почему нельзя определить Nullable <T> .GetType ()? Разве результатом не должно быть typeof (Nullable <T>)?

Drew Noakes 23.10.2008 12:37

Дрю: проблема в том, что GetType () не виртуальный, поэтому он не переопределяется - это означает, что значение помещено в рамку для вызова метода. Поле становится пустой ссылкой, следовательно, NRE.

Jon Skeet 25.10.2008 23:52

@Нарисовался; кроме того, существуют специальные правила упаковки для Nullable <T>, что означает, что пустой Nullable <T> будет иметь значение NULL, а не поле, содержащее пустой Nullable <T> (и пустое поле отключается от пустого Nullable <T >)

Marc Gravell 26.10.2008 11:34

Очень-очень круто. В некотором роде не круто. ;-)

Konrad Rudolph 28.10.2008 00:33

Вау, для меня это довольно неожиданно. Пришлось поиграть в тестовом приложении, чтобы убедиться в этом сам.

Frank Schwieterman 30.06.2009 21:52

Не могли бы вы дать ссылку на ту часть документации по языку, которая объясняет, что означает «where T : new()»?

finnw 04.11.2009 19:30

Конструктор-ограничение, 10.1.5 в спецификации языка C# 3.0

Marc Gravell 04.11.2009 19:39

Вау, спасибо за информацию о ProxyAttribute!

Anton Tykhyy 09.12.2009 15:00

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

finnw 05.02.2010 00:30

@finnw: Я очень надеюсь, что это было саркастично. «Да, давайте превратим жизнь программиста в ад, потому что типы, допускающие значение NULL, будут упакованы в NULL при вызове GetType () в экземпляре, использующем дженерики».

configurator 10.09.2010 03:47

@John Skeet: Правильно ли было бы сказать, что GetType не требовал бы бокса, если бы у Microsoft была каждая структура, автоматически затенявшая Object.GetType с собственной реализацией, но Microsoft этого не сделала, бокс необходим для вызова Object. GetType? Сломало бы это что-нибудь, если бы в .Net 5.0 Microsoft имела структуру (или, по крайней мере, обнуляемые типы), включающую такую ​​теневую реализацию?

supercat 16.04.2011 23:29

@supercat; @Jon не увидит этого, если вы не удалите "h", но: GetType () сам по себе не является виртуальным, и его изменение будет огромным изменением. Конечно, во многих случаях для структур существует нет нужды называть это - компилятор может сделать подстановку (он уже знает); но для этого потребуется изменение спецификации.

Marc Gravell 16.04.2011 23:42

@Marc Gravell: Он увидит ваш комментарий? Я думал, эффективен только первый тег @? Думаю, я вижу проблему. Структура может реализовать GetType таким образом, чтобы затенять Object.GetType, и Nullable <T>, вероятно, тоже может. Это позволило бы вызвать GetType для объекта, о котором было известно, что он имеет тип Nullable <T> без упаковки. Однако это не решит проблему в общем случае, поскольку отправка метода определяется до привязки универсального типа. Можно определить структуру с помощью метода GetType, который возвращает 1.GetType; если вызвать GetType для такой структуры ...

supercat 17.04.2011 00:30

@Marc Gravell: ... результат будет (System.Int32), но если передать такую ​​структуру универсальной функции, которая вызывает для нее GetType, результатом будет тип структуры. Думаю, меня немного удивляет то, что Nullable <T> не затеняет метод GetType, чтобы его можно было использовать без упаковки, и добавление затененного метода GetType к Nullable <T>, вероятно, ничего не сломает, но даже если такой теневой метод не повлияет на необходимость вставки в универсальную функцию. Спасибо за письмо.

supercat 17.04.2011 00:33

В этом посте я впервые увидел переопределение CreateInstance. Я сразу понял, что он делает, и почувствовал, как липкий ужас охватил мою душу ...

Brian Gordon 09.08.2011 22:46

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

Результатом будет «Попытка прочитать защищенную память. Это признак того, что другая память повреждена».

Интересно! Хотя звучит как ошибка компилятора; Я портировал на C#, и он отлично работает. Тем не менее, существует много проблем с исключениями, возникающими в Load, и он ведет себя по-разному с / без отладчика - вы можете поймать с помощью отладчика, но не без него (в некоторых случаях).

Marc Gravell 12.10.2008 01:44

Извините, я забыл, вам нужно добавить поле со списком в форму, прежде чем оно появится.

Joshua 12.10.2008 06:07

Связано ли это с инициализацией диалога с использованием SEH в качестве ужасного механизма внутренней связи? Смутно припоминаю что-то подобное в Win32.

Daniel Earwicker 14.03.2009 20:54

Нет. При подключении неуправляемого отладчика выяснилось, что это разыменование нулевого указателя.

Joshua 14.03.2009 22:53

Это та же проблема cbp выше. Возвращаемый тип значения является копией, поэтому любые ссылки на любые свойства, происходящие из указанной копии, направляются в область битового ведра ... bytes.com/topic/net/answers/…

Bennett Dill 09.10.2009 08:04

Неа. Здесь нет структур. Я действительно отлаживал это. Он добавляет NULL в коллекцию элементов списка собственного поля со списком, вызывая отложенный сбой.

Joshua 09.10.2009 19:24

Банковское округление.

Это не столько ошибка компилятора или неисправность, сколько, конечно, странный угловой случай ...

В .Net Framework используется схема округления, известная как округление Банковского.

В Bankers 'Rounding числа 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.

Мне это тоже показалось странным. По крайней мере, до тех пор, пока я не округлил большой список чисел и не вычислил их сумму. Затем вы понимаете, что если вы просто округляете в большую сторону, вы получите потенциально огромную разницу от суммы неотокругленных чисел. Очень плохо, если вы делаете финансовые расчеты!

Tsvetomir Tsonev 12.10.2008 14:05

Я бы не согласился, работая в финансовой среде. Если ваши бизнес-требования нуждаются в одном способе округления, и если ваше программное обеспечение действует иначе, именно здесь и возникают проблемы. В тяжелых статистических расчетах округление Банкиров более точное - я думаю, это то, что вы имеете в виду.

Samuel Kim 12.10.2008 16:18

Если люди не знали, вы можете сделать: Math.Round (x, MidpointRounding.AwayFromZero); Изменить схему округления.

ICR 12.10.2008 16:24

Из документации: Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или банковским округлением. Это сводит к минимуму ошибки округления, которые возникают в результате последовательного округления среднего значения в одном направлении.

ICR 12.10.2008 16:25

Интересно, поэтому я так часто вижу int(fVal + 0.5) даже на языках, в которых есть встроенная функция округления.

Ben Blank 24.02.2009 22:50

Хороший ответ! Это свело меня с ума !!! Я не мог понять, почему ...

Christian Payne 07.09.2009 02:50

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

dan 29.12.2009 23:43

Я написал приложение для сортировки и агрегирования биржевой торговли в MS Access несколько лет назад (я покаялся, теперь нет нужды в публичном унижении). Если я правильно помню, в нем не было банковского округления и были неприятные побочные эффекты из-за фундаментальной неточности реальных чисел. В конечном итоге мы с командой переписали все это как приложение .Net 1.0 ASP.Net, используя сохраненные процедуры. Как вы могли догадаться, это было немного быстрее.

Steve Hiner 21.01.2010 11:09

@ Бен Бланк: Я использую это просто потому, что мне нужен int, а не округлый поплавок.

mpen 01.03.2010 10:19

Да уж! Это одна из причин, почему мне нравится .NET. Подожди ... Я не знаю !!!

victor hugo 01.05.2010 20:58

Это не просто .net, это, по крайней мере, восходит к VB4. Учитывая, что это не приводит к искажению результатов в большинстве сценариев при агрегировании, почему этот «нормальный» способ округления не преподается в школе?

Jim L 26.05.2010 21:42

@ Джим Леонардо, в школе, 0,5 округляют. В этом коде 0,5 округляется в большую сторону, если вверх четно, и в меньшую сторону, если вверх нечетно. Учитывая следующий массив { 0.5, 1.5, 2.5, 3.5, 4.5, 5.5 }, округление вернет { 0, 2, 2, 4, 4, 6 }.

Anthony Pegram 26.05.2010 21:59

Также обратите внимание, что округление, которому нас учили в школах (или, по крайней мере, тому, чему меня учили в школах), должно было идти на одну цифру больше, чем желаемый результат, а затем округлять. При стандартном округлении IEEE (так называемом «банковском») вам нужно рассматривать 5 по-другому, если есть дополнительный остаток. (Например, 2,5 раунда до 2, но 2,5001 раунда до 3). Я полагаю, что эта дополнительная деталь была слишком велика для ума третьего класса.

phoog 22.12.2010 03:44

На самом деле, если вы посмотрите глубже, это не имеет ничего общего с .Net. Это FPU Intel x86, который использует округление Банкира. Легко найти в гугле.

IamIC 09.04.2011 23:37

гм .. за исключением того, что вы можете выбрать, какой вид округления использовать со вторым параметром Math.Round, как указано в ICR.

Tom 27.05.2011 08:36

Я думаю, что ответ на вопрос заключается в том, что .net использует интернирование строк чем-то, что может привести к тому, что одинаковые строки будут указывать на один и тот же объект (поскольку строки изменяемы, это не проблема)

(Я не говорю о переопределенном операторе равенства в строковом классе)

Строки - это неизменный, не изменяемые. И это не «нормальное» интернирование строк - это происходит только тогда, когда вы передаете пустой массив символов. Однако на самом деле вопрос не в том, «почему это происходит?» но "что подобное вы видели?"

Jon Skeet 12.10.2008 22:04

Напоминает мне, как любое обсуждение проблемы Fizz Buzz приводит к тому, что по крайней мере половина ответов является решением проблемы.

Wedge 14.10.2008 05:41

Половина из которых были неверными.

Joe 21.08.2010 00:24

Интересно - когда я впервые посмотрел на это, я предположил, что это то, что проверяет компилятор 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 14.07.2009 03:29

Вы не ведете себя умно; вы упускаете суть - я хотел создать конкретный IL для этого одного случая. И в любом случае, учитывая, что Reflection.Emit является тривиальным для этого типа сценария, это, вероятно, так же быстро, как написать программу на C#, затем открыть отражатель, найти двоичный файл, найти метод и т.д ... И мне даже не нужно оставьте IDE для этого.

Greg Beech 14.07.2009 13:35

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

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

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

Конечно, когда это FILE_NOT_FOUND!

Greg 18.12.2008 17:47

Это интересно, потому что с математической точки зрения это означает, что ни одно выражение в C# не является доказуемым. Ой.

Simon Johnson 01.02.2009 03:56

@Simon Johnson - вы имеете в виду VB.NET, о котором идет речь в этом посте, верно?

Daniel Earwicker 14.03.2009 20:52

Я тестировал его, и он тоже работает на C#.

RCIX 20.09.2009 04:54

Когда-нибудь я напишу программу, которая будет зависеть от этого поведения, и демоны мрачного ада приготовят мне прием. Бвахахахахаха!

Jeffrey L Whitledge 26.11.2009 01:23

В этом примере используются побитовые, а не логические операторы. Что в этом удивительного?

Josh Lee 26.11.2009 20:02

Ну, он взламывает макет структуры, конечно, вы получите странные результаты, это не так уж удивительно или неожиданно!

user90843 19.05.2010 07:18

Что будет делать эта функция при вызове 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-битной машины, чтобы проверить это, но метод действительно удовлетворяет всем условиям для оптимизации хвостового вызова. Если у кого-то есть, мне было бы интересно узнать, правда ли это.

Должен быть скомпилирован в режиме выпуска, но определенно работает на x64 =)

Neil Williams 12.10.2008 22:43

Возможно, стоит обновить свой ответ, когда выйдет VS 2010, поскольку все текущие JIT будут выполнять TCO в режиме выпуска

ShuggyCoUk 29.06.2009 03:40

Только что попробовал VS2010 Beta 1 на 32-битной WinXP. По-прежнему получаю исключение StackOverflowException.

squillman 27.08.2009 00:45

Да, поддержка хвостового вызова в JIT полезна только в том случае, если компилятор генерирует префиксы хвостового кода операции, чего, похоже, компилятор C# по-прежнему не делает. Однако эквивалентный код F# должен работать отлично. :)

bcat 05.09.2009 08:03

+1 для исключения StackOverflowException

calvinlough 05.01.2010 20:14

У меня не работает на win7 x64 :( выдает исключение переполнения стека

iBiryukov 22.04.2010 04:45

@Ilya - Вы уверены, что компилируете приложение для платформы Any CPU или x64? Visual Studio по умолчанию использует x86, поэтому даже в 64-битной системе вы увидите 32-битное поведение, если вы не измените параметр компиляции.

Greg Beech 22.04.2010 04:51

@Gred, да я уверен :)

iBiryukov 22.04.2010 14:59

@Ilya - Возможно, вы изменили цель сборки на x64 для режима отладки, а затем скомпилировали в Release? Я сам допустил эту ошибку.

Odrade 07.12.2010 21:01

Тот ++ меня совершенно сбил с толку. Разве вы не можете вызвать Rec(i + 1) как нормальный человек?

configurator 25.02.2011 03:32

@configurator ++ i является нормальным для языков стиля C.

IamIC 09.04.2011 23:47

Этот tail call optimization интересен: есть ли аналогичные функции в любой из JVM? Спасибо!

java.is.for.desktop 28.07.2011 20:42

@configurator, разница в том, что оператор i++ возвращает значение переменной, а затем увеличивает его, ++i увеличивает значение и затем возвращает. а оператор ++ намного проще, чем i + 1.

Shimmy Weitzhandler 20.01.2012 01:24

@Shimmy: Чем ++i проще, чем i+1? Оба они представляют собой ровно 3 символа. Далее ++i изменяет значение i, что здесь не нужно (и может сбивать с толку). Все, что требуется, - это передать методу значение «на единицу больше, чем i».

James Curran 16.02.2012 18:43

@configurator - я изменил i++ на i + 1, потому что я согласен с тем, что состояние мутации - плохая форма для метода функционального стиля.

Greg Beech 16.02.2012 22:04

@JamesCurran, согласился с частью ненужного увеличения локальной переменной в текущей рекурсии.

Shimmy Weitzhandler 17.02.2012 04:34

Следующее выводит False вместо исключения переполнения:

Console.WriteLine("{0}", yep(int.MaxValue ));


private bool yep( int val )
{
    return ( 0 < val * 2);
}

Вы можете получить исключение OverflowException, заключив тест в флажок checked {} или установив соответствующий параметр компилятора. Не сразу понятно, почему по умолчанию не установлен флажок ... msdn.microsoft.com/en-us/library/khy08726(VS.71).aspx

stevemegson 06.11.2008 01:53

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

Peter Oehlert 23.12.2008 20:38

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

Peter Oehlert 23.12.2008 20:39

int.MaxValue * 2 - отрицательное число в непроверенной арифметике, которое используется по умолчанию в C#, там для сравнения возвращается false. Это не неожиданное поведение: P

Lucas 12.06.2009 22:38

Этот меня по-настоящему озадачил (прошу прощения за длину, но это WinForm). Я недавно разместил это в группы новостей.

I've come across an interesting bug. I have workarounds but i'd like to know the root of the problem. I've stripped it down into a short file and hope someone might have an idea about what's going on.

It's a simple program that loads a control onto a form and binds "Foo" against a combobox ("SelectedItem") for it's "Bar" property and a datetimepicker ("Value") for it's "DateTime" property. The DateTimePicker.Visible value is set to false. Once it's loaded up, select the combobox and then attempt to deselect it by selecting the checkbox. This is rendered impossible by the combobox retaining the focus, you cannot even close the form, such is it's grasp on the focus.

I have found three ways of fixing this problem.

a) Remove the binding to Bar (a bit obvious)

b) Remove the binding to DateTime

c) Make the DateTimePicker visible !?!

I'm currently running Win2k. And .NET 2.00, I think 1.1 has the same problem. Code is below.

using System;
using System.Collections;
using System.Windows.Forms;

namespace WindowsApplication6
{
    public class Bar
    {
        public Bar()
        {
        }
    }

    public class Foo
    {
        private Bar m_Bar = new Bar();
        private DateTime m_DateTime = DateTime.Now;

        public Foo()
        {
        }

        public Bar Bar
        {
            get
            {
                return m_Bar;
            }
            set
            {
                m_Bar = value;
            }
        }

        public DateTime DateTime
        {
            get
            {
                return m_DateTime;
            }
            set
            {
                m_DateTime = value;
            }
        }
    }

    public class TestBugControl : UserControl
    {
        public TestBugControl()
        {
            InitializeComponent();
        }

        public void InitializeData(IList types)
        {
            this.cBoxType.DataSource = types;
        }

        public void BindFoo(Foo foo)
        {
            this.cBoxType.DataBindings.Add("SelectedItem", foo, "Bar");
            this.dtStart.DataBindings.Add("Value", foo, "DateTime");
        }

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name = "disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.checkBox1 = new System.Windows.Forms.CheckBox();
            this.cBoxType = new System.Windows.Forms.ComboBox();
            this.dtStart = new System.Windows.Forms.DateTimePicker();
            this.SuspendLayout();
            //
            // checkBox1
            //
            this.checkBox1.AutoSize = true;
            this.checkBox1.Location = new System.Drawing.Point(14, 5);
            this.checkBox1.Name = "checkBox1";
            this.checkBox1.Size = new System.Drawing.Size(97, 20);
            this.checkBox1.TabIndex = 0;
            this.checkBox1.Text = "checkBox1";
            this.checkBox1.UseVisualStyleBackColor = true;
            //
            // cBoxType
            //
            this.cBoxType.FormattingEnabled = true;
            this.cBoxType.Location = new System.Drawing.Point(117, 3);
            this.cBoxType.Name = "cBoxType";
            this.cBoxType.Size = new System.Drawing.Size(165, 24);
            this.cBoxType.TabIndex = 1;
            //
            // dtStart
            //
            this.dtStart.Location = new System.Drawing.Point(117, 40);
            this.dtStart.Name = "dtStart";
            this.dtStart.Size = new System.Drawing.Size(165, 23);
            this.dtStart.TabIndex = 2;
            this.dtStart.Visible = false;
            //
            // TestBugControl
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.dtStart);
            this.Controls.Add(this.cBoxType);
            this.Controls.Add(this.checkBox1);
            this.Font = new System.Drawing.Font("Verdana", 9.75F,
            System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point,
            ((byte)(0)));
            this.Margin = new System.Windows.Forms.Padding(4);
            this.Name = "TestBugControl";
            this.Size = new System.Drawing.Size(285, 66);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.CheckBox checkBox1;
        private System.Windows.Forms.ComboBox cBoxType;
        private System.Windows.Forms.DateTimePicker dtStart;
    }

    public class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Load += new EventHandler(Form1_Load);
        }

        void Form1_Load(object sender, EventArgs e)
        {
            InitializeControl();
        }

        public void InitializeControl()
        {
            TestBugControl control = new TestBugControl();
            IList list = new ArrayList();
            for (int i = 0; i < 10; i++)
            {
                list.Add(new Bar());
            }
            control.InitializeData(list);
            control.BindFoo(new Foo());
            this.Controls.Add(control);
        }

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name = "disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Text = "Form1";
        }

        #endregion
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

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

Пример IList <T> - это просто приведение, потому что string [] уже реализует ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> и IEnumerable <string>.

Lucas 12.06.2009 22:46

Я немного опаздываю на вечеринку, но у меня пять тричетыре:

  1. Если вы опрашиваете InvokeRequired для элемента управления, который не был загружен / показан, он скажет false - и взорвется вам в лицо, если вы попытаетесь изменить его из другого потока (решение должен ссылаться на это. ).

  2. Еще один, который меня сбил с толку, - это сборка с:

    enum MyEnum
    {
        Red,
        Blue,
    }
    

    если вы вычисляете MyEnum.Red.ToString () в другой сборке, и в промежутке между ними кто-то перекомпилировал ваше перечисление, чтобы:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }
    

    во время выполнения вы получите «черный».

  3. У меня была общая сборка с некоторыми удобными константами. Мой предшественник оставил кучу уродливых свойств только для получения, я подумал, что избавлюсь от беспорядка и просто буду использовать public const. Я был более чем немного удивлен, когда VS скомпилировал их по своим значениям, а не по ссылкам.

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

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

№2 - интересный пример. Перечисления - это сопоставления компилятора с целыми значениями. Таким образом, даже если вы явно не присвоили им значения, компилятор это сделал, в результате чего MyEnum.Red = 0 и MyEnum.Blue = 1. Когда вы добавили черный цвет, вы переопределили значение 0 для отображения с красного на черный. Я подозреваю, что проблема могла бы проявиться и в других случаях использования, таких как сериализация.

LBushkin 25.05.2009 07:02

Требуется +1 для призыва. В нашем случае мы предпочитаем явно присваивать значения перечислениям, например Red = 1, Blue = 2, поэтому новое значение может быть вставлено до или после того, как оно всегда будет приводить к одному и тому же значению. Это особенно необходимо, если вы сохраняете значения в базы данных.

TheVillageIdiot 04.06.2009 13:40

@ aman.tur, возможно, вас заинтересует этот вопрос: stackoverflow.com/questions/881726/…

Benjol 04.06.2009 15:26

Я не согласен с тем, что № 5 - это «крайний случай». Словарь не должен иметь определенный порядок, основанный на том, когда вы вставляете значения. Если вам нужен определенный порядок, используйте список или используйте ключ, который может быть отсортирован удобным для вас способом, или используйте совершенно другую структуру данных.

Wedge 26.06.2009 12:30

Ну, я думаю, это не крайний случай, но это определенно ошибка (по крайней мере, для меня)

Benjol 26.06.2009 12:38

Я потратил много часов на отладку ошибки, связанной с InvokeRequired - это было очень неприятно.

Tamás Szelei 11.08.2009 12:07

@Wedge, например, SortedDictionary?

Allon Guralnek 22.08.2009 20:54

Словарь определенно сделан по дизайну, так как отсортирован по хеш-коду ... не так ли?

John Gietzen 04.09.2009 17:57

# 3 происходит потому, что константы вставляются как литералы везде, где они используются (по крайней мере, в C#). Ваш предшественник, возможно, уже заметил это, поэтому они использовали свойство только для получения. Однако переменная только для чтения (в отличие от константы) будет работать точно так же.

Remoun 30.09.2009 13:21

№1 - ужасный случай, который нас сильно укусил. Здесь есть отличное объяснение: 209.85.129.132/search?q=cache:www.ikriv.com/en/prog/info/dot‌ net /… (из Google Cache, потому что сайт, кажется, не работает). Это очень хорошая статья, написанная как загадочный случай.

Omer Mor 28.02.2010 12:26

# 2 Еще одна проблема с перечислениями C# ... # 5 Так работают хеш-таблицы. Если вам нужно его заказать, используйте SortedDictionary

BlueRaja - Danny Pflughoeft 26.05.2010 21:55

Вот пример того, как вы можете создать структуру, которая вызывает сообщение об ошибке «Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена». Разница между успехом и неудачей очень тонкая.

Следующий модульный тест демонстрирует проблему.

Посмотри, сможешь ли ты понять, что пошло не так.

    [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 14.07.2009 03:44

Второй вопрос - можно ли получить объяснение?

SqlRyan 05.08.2009 22:58

Хм, я написал это несколько месяцев назад, но не могу вспомнить, почему именно это произошло.

cbp 06.08.2009 10:50

Похоже на ошибку компилятора; += 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), а затем решает сделать это другим способом.

Marc Gravell 12.09.2009 14:17

Структура - это ValueType, поэтому свойство возвращает копию. Нет смысла пытаться обновить копию типа значения, которая попадает в битовое ведро. Если вы хотите обновить это значение, вы, вероятно, захотите ... MyStruct Temp = bar.Foo; Темп + = 500; bar.Foo = Temp;

Bennett Dill 09.10.2009 07:56

Извините за двойной пост, вот более подробное объяснение. Я добавлю это, меня это укусило, и это отстой, хотя я понимаю, почему это происходит. Для меня это досадное ограничение типа структуры / значения. bytes.com/topic/net/answers/…

Bennett Dill 09.10.2009 07:59

@Ben получает ошибки компилятора или модификация, не влияющая на исходную структуру, в порядке. Нарушение прав доступа - совсем другое дело. Среда выполнения никогда не должна бросать его, если вы просто пишете безопасный чистый управляемый код.

CodesInChaos 20.11.2010 00:59

@configurator: исправлено в C# 4.0

Cheng Chen 21.04.2011 07:34

Несколько лет назад при работе над программой лояльности у нас возникла проблема с количеством начисляемых клиентам баллов. Проблема была связана с приведением / преобразованием double в 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);

Приведение к целому числу не округляется, а просто обрезается (фактически всегда округляется в меньшую сторону). Так что в этом есть смысл.

Max Schmeling 14.05.2009 21:34

@Max: да, но почему Convert round?

Stefan Steinegger 19.05.2009 15:07

@Stefan Steinegger Если бы все, что он делал, было брошено, для этого не было бы вообще никакой причины, не так ли? Также обратите внимание, что имя класса - Convert not Cast.

bug-a-lot 04.08.2009 17:35

В VB: раунды CInt (). Fix () усекает. Сжег меня однажды (blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.ht‌ мл)

Michael Haren 28.08.2009 00:56

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

Я знал, что основная команда разработчиков 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");
        }
    }
}

Вау, это новость для меня. Также странно, как работает ConverT.ToInt32 (), но приведение к (int) 0 - нет. И любое другое число> 0 работает. (Под «работает» я подразумеваю вызов перегрузки объекта.)

Lucas 12.06.2009 22:52

Существует рекомендуемое правило анализа кода для применения передовых практик в отношении этого поведения: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Эта ссылка также содержит хорошее описание того, как работает 0-отображение.

Chris Clark 16.12.2009 23:14

@Chris Clark: я попытался поставить None = 0 в enum Symbol. по-прежнему компилятор выбирает enum для 0 и даже (int) 0

Michael Buen 17.12.2009 03:51

Не думаю, что Крис понял истинную суть вашего поста. Не имеет ничего общего с перечислениями как флагами.

Jimbo 16.03.2010 22:05

Это определено в спецификации, и я думаю, причина в том, что 0 является значением по умолчанию для всех перечислений. Я не программист-компилятор, но я предполагаю, что другой вариант - инициализировать все перечисления первым значением в их списке значений. Что на самом деле было бы намного большей чепухой.

Gorkem Pacaci 14.07.2010 01:22

IMO они должны были ввести ключевое слово none, которое можно использовать для преобразования в любое перечисление, и сделать 0 всегда int и неявно преобразованным в перечисление.

CodesInChaos 20.11.2010 00:24

ConverTo.ToIn32 () работает, потому что его результат не является константой времени компиляции. И только константа времени компиляции 0 может быть преобразована в перечисление. В более ранних версиях .net даже буквальный 0 должен был быть преобразован в enum. См. Блог Эрика Липперта: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx

CodesInChaos 20.11.2010 00:27

Дело не в преждевременной оптимизации. Я стремлюсь к ортогональности и согласованности языка или его паттернов. В самом деле, только константа 0 может быть преобразована в перечисление; но если 1 соответствует целому числу, почему не 0? (Я знаю ответ, это как-то связано с традициями языка C и приветствием программистов из этого языкового лагеря).

Michael Buen 20.11.2010 19:49

Вау ... я не знала об этом ... действительно шокирует ... Спасибо ...

Shekhar_Pro 10.01.2011 15:30

Если вы хотите вызвать перегрузку object, почему вы выполняете приведение к int? JustTest((object)0) должен звонить правильно.

configurator 25.02.2011 03:35

Область видимости в C# временами бывает действительно причудливой. Приведу один пример:

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

OleDbCommand command = SQLServer.CreateCommand();

Это не может быть скомпилировано, потому что команда повторно объявлена? Есть некоторые интересные предположения относительно того, почему это работает таким образом в этом поток в stackoverflow и в мой блог.

Я не считаю это особенно странным. То, что вы называете «совершенно правильным кодом» в своем блоге, в соответствии со спецификацией языка, является неверный. Это может быть правильным на каком-то воображаемом языке, которым вы бы хотели быть нравиться C#, но в спецификации языка совершенно ясно, что в C# это недопустимо.

Jon Skeet 26.06.2009 12:48

Ну действительно в C / C++. И поскольку это C#, мне бы хотелось, чтобы он по-прежнему работал. Что меня больше всего беспокоит, так это то, что у компилятора нет причин делать это. Вложенная область видимости не сложна. Думаю, все сводится к элементу наименьшего удивления. Это означает, что может быть так, что спецификация говорит то или это, но это мне не очень помогает, если совершенно нелогично, что он ведет себя таким образом.

Anders Rune Jensen 26.06.2009 13:08

C#! = С / С ++. Хотели бы вы также использовать cout << "Hello World!" << endl; вместо Console.WriteLine ("Hello World!") ;? Также это не нелогично, просто прочтите спецификацию.

Kredns 28.06.2009 22:32

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

Anders Rune Jensen 29.06.2009 20:11

Эрик Липперт недавно опубликовал причины, по которым C# так спроектирован: blogs.msdn.com/ericlippert/archive/2009/11/02/…. Резюме сделано потому, что менее вероятно, что изменения будут иметь непредвиденные последствия.

Helephant 25.11.2009 20:16

Спасибо. Это проливает на него довольно много света. Но на самом деле его примеры мне кажутся неправдоподобными. Пример 1 просто выдал бы предупреждение на C++. Я действительно не вижу в них проблемы. Написание такой большой функции также является небрежной практикой программирования, поэтому я не вижу смысла отказываться от языка только для того, чтобы соответствовать людям, которые не могут писать правильный код ;-)

Anders Rune Jensen 25.11.2009 23:49

Вот один, о котором я узнал только недавно ...

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

Вышеупомянутое на первый взгляд выглядит сумасшедшим, но это фактически законный. Нет, на самом деле (хотя я пропустил ключевую часть, но это не что-то хакерское, например «добавить класс с именем IFoo» или «добавить псевдоним using, чтобы указать IFoo на класс").

Посмотрим, сможешь ли ты понять почему, тогда: Кто сказал, что вы не можете создать интерфейс?

+1 за "использование псевдонима" - я никогда не знал, что вы можете сделать который!

David 09.02.2010 07:37

взломать компилятор для COM Interop :-)

user90843 03.06.2011 23:08

Сволочь! Вы могли бы хотя бы сказать «при определенных обстоятельствах» ... Мой компилятор опровергает!

M.A. Hanin 09.11.2011 00:18

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

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

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

[шутка] Думаю, это черепахи ... [/ шутка]

Однако вы можете создавать экземпляры: class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();

Marc Gravell 26.08.2009 10:55

Верно. Это паттерн, который с большим эффектом используют перечисления Java. Я тоже использую его в буферах протокола.

Jon Skeet 26.08.2009 11:09

Но вы не можете этого сделать, поскольку RealTurtle - это не Черепаха <RealTurtle> ....

RCIX 26.08.2009 11:56

RCIX, да, это так.

Joshua 01.09.2009 20:22

Я довольно часто использовал этот паттерн в причудливых дженериках. Он позволяет делать такие вещи, как правильно типизированный клон или создавать собственные экземпляры.

Lucero 10.09.2009 23:31

Я не знаю. Возможно, это не является незаконным, но это определенно одна из самых странных вещей, которые я когда-либо видел.

RCIX 20.09.2009 04:47

Это «любопытно повторяющийся шаблон шаблона» en.wikipedia.org/wiki/Curiously_recurring_template_pattern

porges 05.11.2009 22:30

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

flq 27.11.2009 12:02

Это совершенно верно и может быть очень полезно, чтобы позволить базовому классу ссылаться на тип дочернего класса (например, Перечисления Java использует его, чтобы убедиться, что compareTo может передавать только значения этого перечисления). -1 ...

BlueRaja - Danny Pflughoeft 26.05.2010 21:51

Это не «странно повторяющийся шаблон шаблона», поскольку это не шаблоны, а общие. Не педантично. Использование шаблона в C++ не работает с универсальными шаблонами C#.

wekempf 01.11.2010 22:10

+1 за глупую шутку про черепаху.

Tom Chantler 01.04.2011 04:27

Я действительно использовал этот шаблон.

bevacqua 16.06.2011 21:18

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

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

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

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

Удаление этой строки приведет к переполнению стека где-то еще в программе.

Оказалось, что код устанавливает обработчик для события BeforeAssemblyLoad и пытается выполнить

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

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

Я предполагаю, что «загрузка сборки» - это побочный эффект, поскольку вы можете наблюдать это с помощью BeforeAssemblyLoad!

Jacob Krall 14.10.2009 00:10

Вау. Это отличный выстрел в ногу обслуживающему персоналу. Думаю, написание обработчика BeforeAssemblyLoad может привести к множеству таких сюрпризов.

wigy 01.02.2011 22:05

VB.NET, значения NULL и тернарный оператор:

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.

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

GSerg 22.03.2011 18:48

PropertyInfo.SetValue () может присваивать целые числа перечислениям, целые числа - целым значениям, допускающим значение NULL, перечисления - перечислениям, допускающим значение NULL, но не целым числам - перечислениям, допускающим значение NULL.

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

Полное описание здесь

Назначьте это!


Это то, что я люблю спрашивать на вечеринках (наверное, поэтому меня больше не приглашают):

Сможете ли вы скомпилировать следующий фрагмент кода?

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

Простым читом может быть:

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

Но настоящее решение таково:

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

Итак, малоизвестный факт, что типы значений (структуры) могут переназначать свою переменную this.

Классы C++ тоже могут это делать ... как я обнаружил недавно, только для того, чтобы на меня накричали за то, что они действительно пытались использовать его для оптимизации: p

mpen 01.03.2010 10:48

На самом деле я использовал новый на месте. Просто хотел эффективный способ обновить все поля :)

mpen 13.03.2010 03:06

Это тоже чит: //this = new Teaser(); :-)

AndrewJacksonZA 18.03.2010 14:06

:-) Я бы предпочел эти читы в моем производственном коде, чем эту мерзость переназначения ...

Omer Mor 25.03.2010 00:49

Из CLR через C#: они сделали это потому, что вы можете вызвать конструктор структуры без параметров в другом конструкторе. Если вы хотите инициализировать только одно значение структуры и хотите, чтобы другие значения были равны нулю / нулю (по умолчанию), вы можете написать public Foo(int bar){this = new Foo(); specialVar = bar;}. Это неэффективно и не совсем оправдано (specialVar назначается дважды), но просто к сведению. (Это причина, указанная в книге, я не знаю, почему мы не должны просто делать public Foo(int bar) : this())

kizzx2 30.01.2011 20:40

@Mark, вы действительно уверены, что можете переназначить само «это»? Вы не говорите о присвоении "* this"?

dascandy 28.07.2011 17:45

@dascandy: Ну, вы перезаписываете часть памяти, которую занимает this, IIRC. Интерпретируйте это как хотите.

mpen 28.07.2011 22:35

Тизер @this; @this = новый тизер (); // Также подойдет для класса.

Olivier Jacot-Descombes 12.11.2011 22:56

Следующее может быть общими знаниями, которых мне просто не хватало, но да. Некоторое время назад у нас был случай с ошибкой, который касался виртуальных свойств. Немного абстрагируясь от контекста, рассмотрите следующий код и примените точку останова к указанной области:

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 меня просветил. При входе в Quickwatch и изучении объекта d Derived (или из контекста объекта, this) и выборе поля base в поле редактирования в верхней части Quickwatch отображается следующее приведение:

((TestProject1.Base)(d))

Это означает, что при замене базы как таковой вызов будет

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

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

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

"Oi, wait..what ? omg that DLL is like, ..doing something funny"

Ничего особенного, просто так работают переопределения.

configurator 25.02.2011 03:54

Я не уверен, что это странность 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.

Это действительно перспективное (а не 7, афайк) улучшение безопасности. Но круто то, что вы можете прочитать и открыть файл с путем к программным файлам, а если вы посмотрите туда с помощью проводника, там ничего нет. Это заняло у меня почти день работы с клиентом, прежде чем я наконец узнал об этом.

Henri 12.03.2010 01:02

Это определенно вещь Windows 7. Это то, что я использовал, когда столкнулся с этим. Я понимаю причину этого, но все равно было сложно понять.

Spencer Ruport 12.03.2010 07:35

В Vista / Win 7 (технически WinXP тоже) приложения должны записывать в папку AppData на земле папок «Пользователи» в качестве своих технически пользовательских данных. Приложения не должны писать в файлы программ / windows / system32 / и т. д., Если у них нет прав администратора, и эти привилегии должны быть там только для того, чтобы сказать: обновить программу / удалить ее / установить новую функцию. НО! По-прежнему не пишите в system32 / windows / etc :) Если вы запустили этот код выше от имени администратора (щелкните правой кнопкой мыши> запустить от имени администратора), он теоретически должен записать в папку приложения программных файлов.

Steve Syfuhs 01.04.2010 02:15

Похоже на виртуализацию - crispybit.spaces.live.com/blog/cns!1B71C2122AD43308!134.entr‌ y

Colin Newell 01.06.2010 14:51

Это самое странное, с чем я столкнулся случайно:

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:

The binary + operator performs string concatenation when one or both operands are of type string. If an operand of string concatenation is null, an empty string is substituted. Otherwise, any non-string operand is converted to its string representation by invoking the virtual ToString method inherited from type object. If ToString returns null, an empty string is substituted.

Хм, но я могу представить себе эту ошибку, поскольку .ToString действительно никогда не должен возвращать null, а string.Empty. Тем не менее и ошибка в рамках.

Dykam 01.03.2010 10:44

В C# есть что-то действительно захватывающее - то, как он обрабатывает замыкания.

Вместо того, чтобы копировать значения переменных стека в переменную, свободную от замыканий, он выполняет эту магию препроцессора, оборачивая все вхождения переменной в объект и, таким образом, перемещает его из стека - прямо в кучу! :)

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

Мне это очень порадовало, спасибо, ребята, MS!

Хотя это не странность или угловой случай ... а что-то действительно неожиданное из языка виртуальных машин на основе стека :)

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

Условный оператор не может привести к неявному преобразованию?

Дано:

Bool aBoolValue;

Где aBoolValue присвоено значение "Истина" или "Ложь";

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

Byte aByteValue = aBoolValue ? 1 : 0;

Но это могло бы:

Int anIntValue = aBoolValue ? 1 : 0;

Предоставленный ответ тоже довольно хорош.

хотя Ive not test it Iм уверен, что это сработает: Byte aByteValue = aBoolValue? (Байт) 1: (Байт) 0; Или: Byte aByteValue = (Byte) (aBoolValue? 1: 0);

Alex Pacurar 10.05.2010 16:59

Да, Алекс, это сработает. Ключ кроется в неявном приведении типов. Только 1 : 0 будет неявно приводить к типу int, а не Byte.

MPelletier 14.05.2010 07:07

Вы когда-нибудь думали, что компилятор 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 ...

Jon Skeet 08.04.2010 23:11

В моей системе это не работает. Я вставлю вывод в вопрос.

Jordão 09.04.2010 20:27

В .NET 3.5 у меня ничего не вышло (нет времени тестировать 4.0). И я могу воспроизвести проблему с кодом VB.NET.

Mark Hurd 04.09.2010 09:35

Что делать, если у вас есть универсальный класс, в котором есть методы, которые могут быть неоднозначными в зависимости от аргументов типа? Я столкнулся с этой ситуацией недавно, когда писал двусторонний словарь. Я хотел написать симметричные методы 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..."

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

Противникам перегрузки методов он понравится ^^.

Christian Klauser 09.09.2010 01:43

Я не знаю, для меня это имеет смысл.

Scott Whitlock 18.03.2011 20:02

В используемом нами 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().

не совсем угловой случай C#, но дорогой господин, кто это придумал?!?

Ray Booysen 22.06.2010 12:03

Разве этот код не использует только типы, допускающие значение NULL? Поэтому ReSharper рекомендует знак "??" использовать. Как сказал Рэй, я бы не подумал, что это угловой случай; или я ошибаюсь?

Tony 22.06.2010 18:14

Да, типы допускают значение NULL - кроме того, существует NullObject. Если это угловой случай, я не знаю, но, по крайней мере, это тот случай, когда 'if (a! = Null) return a; return b; ' это не то же самое, что «вернуть ?? б '. Я абсолютно согласен с тем, что это проблема с дизайном фреймворка / API - перегрузка == null для возврата true для объекта, конечно, не лучшая идея!

Tor Livar 22.06.2010 18:47

Следующее не работает:

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

Но это работает:

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

Я не понимаю, почему это угловой случай ... В первом примере вы не можете использовать переменную v, так как ее область действия - блок else, и вы можете иметь в ней только одну инструкцию, если вы этого не сделаете. я поставил брекеты

Thomas Levesque 10.08.2010 19:44

Я не вижу разницы в двух фрагментах кода.

Benny 11.08.2010 10:22

@ Томас: Да, но почему это ошибка? Я мог бы захотеть добавить это выражение, чтобы иметь возможность разбить предложение else. В C++ это совершенно верно. Мне неприятно, что существует семантическая разница между else {} и else, когда в предложении есть только одно выражение.

Anders Rune Jensen 11.08.2010 12:39

@Anders: В ваших ответах большое внимание уделяется тому факту, что C# отличается от C++, например, здесь: stackoverflow.com/questions/194484/… Этот поток не касается различий между C# и C++. Пограничный регистр в C# ничем не отличается от C++. Другие отметили, что вы найдете ответы в спецификации C#.

John K 11.08.2010 17:04

@jdk: Я добавил примечание C++ для полноты. Я согласен с тем, что это может быть не самый большой крайний случай, который я видел, меня просто удивило, когда я нашел его вчера.

Anders Rune Jensen 11.08.2010 23:18

После небольшого размышления я вижу, что это поможет людям, плохо знакомым с программированием, и почти не повредит нормальным программистам. Так что вместо того, чтобы называть это угловым случаем, можно было бы назвать это особенностью :-)

Anders Rune Jensen 18.08.2010 13:40

C# Задача доступности


Следующий производный класс обращается к частное поле из своего базового класса, и компилятор молча смотрит на другую сторону:

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. Иногда это может быть полезной функцией, особенно если внешний класс статичен.

user438034 20.10.2010 04:02

Да, конечно, это задокументировано. Однако очень немногие люди решали эту головоломку, поэтому я подумал, что это классная мелочь.

Omer Mor 20.10.2010 11:51

Похоже, у вас будет очень большая вероятность переполнения стека, если внутренний класс унаследует своего владельца ...

Jamie Treworgy 28.10.2010 20:58

Еще один похожий (и совершенно правильный) случай - объект может получить доступ к закрытому члену другого объекта того же типа: class A { private int _i; public void foo(A other) { int res = other._i; } }

Olivier Jacot-Descombes 13.11.2011 00:02

Если у вас есть метод расширения:

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

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

и этот код:

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

Это не вызовет исключения, потому что это метод расширения (и на самом деле HelperClass.ToBoolean(null)), а не метод экземпляра. Это может сбивать с толку.

Я не думаю, что это какой-то странный угловой вариант, более заурядный синтаксис. Такое поведение позволяет вам делать такие вещи, как static void IfNotNull<T>(Action<T> action) ... Если у вашего метода расширения есть проблема с нулевым параметром this, то бросьте ArgumentNullException.

Keith 23.08.2010 01:39

@Keith Это, безусловно, может быть полезно, но когда вы посмотрите на это (с точки зрения Java, C++, C# 2), это будет странно, и как разработчику C# 3+ вам все равно придется проверять, действительно ли это метод расширения (не в строках, а в более сложных примерах), а не в методе экземпляра, где они (другой код) забыли нулевую проверку.

Lasse Espeholt 23.08.2010 08:25

Я полагаю, что моя точка зрения заключается в том, что метод расширения лучше во всех местах, где вы бы использовали его, а не метод экземпляра. Возьмите свой примерный метод: он возвращает bool? - вполне приемлемо (даже предпочтительно), чтобы ваш nullStr.ToBoolean() возвращал null, а не выдавал NullReferenceException

Keith 23.08.2010 16:57

Я думаю, он говорит, что если бы вы унаследовали код и увидели фрагмент, не зная определения метода расширения, это бы сбило с толку.

Michael Blackburn 03.04.2011 08:30

Я думаю, им следовало использовать другой символ. Как конвейер в F#. nullStr|>ToBoolean или nullStr->ToBoolean.

Lasse Espeholt 03.04.2011 22:31

вот несколько моих:

  1. это может быть null при вызове метода экземпляра без выброса NullReferenceException
  2. значение перечисления по умолчанию не обязательно должно быть определено для перечисления

Сначала простой: перечисление NoZero { Число = 1 }

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

Приведенный ниже код действительно может печатать истину!

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

Простой фрагмент клиентского кода, который приведет к тому, что делегат void HelloDelegate (Странная полоса);

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

это действительно так для большинства языков, пока метод экземпляра при вызове не использует состояние объекта. это разыменовывается только при обращении к состоянию объекта

Случай с перечислением на самом деле не удивителен, типом, лежащим в основе перечисления по умолчанию, является int, поэтому по умолчанию перечисление вернет 0, что действительно не определено в NoZero. Даже при указании настраиваемого типа (в пределах byte, sbyte, short, ushort, int, uint, long или ulong) для вашего перечисления значение по умолчанию для всех этих типов по-прежнему равно 0.

Dynami Le Savard 14.10.2010 20:35

@Dynami да, это из-за значения по умолчанию для базового типа, но (для меня) довольно бессмысленно иметь недопустимое значение по умолчанию для типа значения, которое он имеет (1, -1) по умолчанию для int. Значение просто не имеет смысла в контексте данного типа

Rune FS 14.10.2010 21:40

Рассмотрим этот странный случай:

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.

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

Это действительно звучит странно. Придется разобраться позже :)

Jon Skeet 05.01.2011 17:16

@Jon Skeet: Я нашел это, исследуя стратегии реализации для роли в C#. Было бы здорово получить ваш отзыв об этом!

Jordão 05.01.2011 20:19

Только что нашел сегодня приятную вещицу:

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 (материал как объект); он работает отлично, однако здесь это кажется "волшебным словом", поскольку он делает то же самое, все по-прежнему воспринимается как динамическое ...

Это довольно просто, но я все же считаю его несколько интересным. Каким будет значение x после вызова Foo?

static int x = 0;

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

static void Main() { Foo(); }

Каков крайний случай в вашем ответе?

Cheng Chen 09.02.2011 08:04

Максим: Верно. Дэнни: Это не совсем крайний случай, но он сочетается с крайними случаями - это то, что нелегко отследить, особенно когда вы работаете с чьим-то кодом.

Andrew Sevastian 10.02.2011 02:23

Это довольно сложно превзойти. Я столкнулся с этим, когда пытался создать реализацию 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) 

Другие вопросы по теме