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

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

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

Я ожидал бы, что напечатать False - в конце концов, "новый" (со ссылочным типом) всегда создает новый объект, не так ли? Спецификации для С# и CLI указывают на то, что это должно быть. Ну, не в этом конкретном случае. Он печатает True и делает на каждой версии рамки, с которой я ее тестировал. (Я не пробовал это на Моно, правда...)

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

Любые другие драгоценные камни скрываются там?

Ответы

Ответ 1

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

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

Ответ: any 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. Теперь иди и мойте глаза!

Ответ 2

Округление банкиров.

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

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

В округлении банкиров число 0,5 округляется до ближайшего четного числа, поэтому

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

Это может привести к неожиданным ошибкам в финансовых расчетах, основанных на более известном округлении Round-Half-Up.

Это также относится к Visual Basic.

Ответ 3

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

Ответ 4

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


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

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

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

Легкий чит может быть:

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

Но реальное решение таково:

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

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

Ответ 5

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

В коде ниже:

double d = 13.6;

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

делает i1 == i2?

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

i1 == 14
i2 == 13

Всегда лучше называть Math.Ceiling() или Math.Floor() (или Math.Round с MidpointRounding, который соответствует нашим требованиям)

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

Ответ 6

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

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

Ответ 7

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

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

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

[шутка] Я думаю, что черепахи полностью опущены... [/joke]

Ответ 8

Здесь я узнал только недавно...

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

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

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

Ответ 9

Когда существует логическое значение: ни True, ни False?

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

Hacked Booleans

Ответ 10

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

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

  • Другая, которая меня подстегнула, - это то, что с учетом:

    enum MyEnum
    {
        Red,
        Blue,
    }
    

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

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }
    

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

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

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

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

Ответ 11

VB.NET, nullables и тернарный оператор:

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.

Ответ 12

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

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

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

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

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

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

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

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

Ответ 13

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

Следующая unit test демонстрирует проблему.

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

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

Ответ 14

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

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

Ответ 15

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

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

Используется следующим образом:

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

Будет выброшено NullReferenceException. Оказывается, множественные добавления скомпилированы компилятором С# для вызова 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:

Оператор binary + выполняет конкатенацию строк, когда один или оба операнда имеют тип string. Если операнд конкатенации строк null, пустая строка заменяется. В противном случае любой нестроковый операнд преобразуется в его строковое представление, вызывая виртуальный метод ToString, унаследованный от типа object. Если ToString возвращает null, заменяется пустая строка.

Ответ 16

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

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

Ответ 17

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

Выход: "Попытка чтения защищенной памяти. Это указывает на повреждение другой памяти".

Ответ 18

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

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

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

Ответ 19

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

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

Ответ 20

С# Доступность 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;
        }
    }
}

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

Ответ 21

Недавно нашла приятную мелочь:

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

Это генерирует ошибку компиляции.

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

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

Ответ 22

В 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().

Ответ 23

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

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.

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

Ответ 24

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

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 и исследуете объект 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, в результате чего разработчики просто говорят:

"Oi, подождите... что? omg, что DLL например,... что-то смешное"

Ответ 25

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

Ответ 26

Я не уверен, скажете ли вы, что это странность 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

Ответ 27

Вы когда-нибудь думали, что компилятор С# может генерировать недопустимый 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));
  }
}

Я не знаю, как это работает в компиляторе С# 4.0.

EDIT: это вывод из моей системы:

C:\Temp>type Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

  interface I<T> {
    T M(T p);
  }
  abstract class A<T> : I<T> {
    public abstract T M(T p);
  }
  abstract class B<T> : A<T>, I<int> {
    public override T M(T p) { return p; }
    public int M(int p) { return p * 2; }
  }
  class C : B<int> { }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine(new C().M(11));
    }
  }

}
C:\Temp>csc Program.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.


C:\Temp>Program

Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli
cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo
ken=null'.
   at ConsoleApplication1.Program.Main(String[] args)

C:\Temp>peverify Program.exe

Microsoft (R) .NET Framework PE Verifier.  Version  3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[token  0x02000005] Type load failed.
[IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x
00000001] Unable to resolve token.
2 Error(s) Verifying Program.exe

C:\Temp>ver

Microsoft Windows XP [Version 5.1.2600]

Ответ 28

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

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

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

Это очень радует меня, спасибо, ребята из MS!

Это не странный или угловой случай, хотя... но что-то действительно неожиданное из языка VM на основе стека:)

Ответ 29

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

Условный оператор нельзя использовать неявно?

Дано:

Bool aBoolValue;

Где aBoolValue назначается либо True, либо False;

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

Byte aByteValue = aBoolValue ? 1 : 0;

Но это:

Int anIntValue = aBoolValue ? 1 : 0;

Полученный ответ тоже хорош.

Ответ 30

Сфера охвата С# поистине странная. Позволяет мне привести один пример:

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

OleDbCommand command = SQLServer.CreateCommand();

Это не скомпилируется, потому что команда обновлена? Есть некоторые догадки относительно того, почему это работает в этом потоке fooobar.com/info/4568/... и в my блог.