Скрытые возможности C#?

Это пришло мне в голову после того, как я узнал следующее из этого вопроса :

where T : struct

Все мы, разработчики C#, знаем основы C#. Я имею в виду объявления, условия, циклы, операторы и т. Д.

Некоторые из нас даже освоили такие вещи, как Generics , анонимные типы , лямбды , LINQ , ...

Но каковы самые скрытые особенности или хитрости C#, о которых едва ли знают даже фанаты C#, наркоманы и эксперты?

Вот обнаруженные на данный момент особенности:


Ключевые слова

Атрибуты

Синтаксис

Особенности языка

Возможности Visual Studio

Фреймворк

Методы и свойства

Советы и хитрости

  • Хороший метод для обработчиков событий от Андреаса Х.Р. Нильссона
  • Сравнение прописных букв Джона
  • Доступ к анонимным типам без отражения через dp
  • Быстрый способ ленивого создания экземпляров свойств коллекции Уиллом
  • Анонимные встроенные функции в стиле JavaScript от roosteronacid

Другой

Ответов (25)

Вот несколько интересных скрытых функций C# в виде недокументированных ключевых слов C#:

__makeref

__reftype

__refvalue

__arglist

Это недокументированные ключевые слова C# (даже Visual Studio распознает их!), Которые были добавлены для более эффективной упаковки / распаковки до универсальных. Они работают в координации со структурой System.TypedReference.

Также существует __arglist, который используется для списков параметров переменной длины.

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

Самая полезная «скрытая» функция - это ключевое слово yield return. На самом деле это не скрыто, но многие об этом не знают. LINQ построен поверх этого; он позволяет выполнять запросы с задержкой, создавая под капотом конечный автомат. Раймонд Чен недавно опубликовал подробности о внутренних деталях .

" уступить " пришло бы мне в голову. Некоторые из атрибутов, например [DefaultValue ()] , также входят в число моих любимых.

Ключевое слово " var " немного более известно, но то, что вы можете использовать его и в приложениях .NET 2.0 (если вы используете компилятор .NET 3.5 и настроили его на вывод кода 2.0), похоже, не очень известно. хорошо.

Изменить: kokos, спасибо, что указали на ?? оператор, это действительно очень полезно. Поскольку это немного сложно найти в Google (поскольку ?? просто игнорируется), вот страница документации MSDN для этого оператора: ?? Оператор (Справочник по C#)

Это не C# как таковой, но я не видел никого, кто действительно использовал бы его System.IO.Path.Combine() в той степени, в которой он должен. Фактически, весь класс Path действительно полезен, но никто им не пользуется!

Я готов поспорить, что каждое производственное приложение имеет следующий код, хотя он и не должен:

string path = dir + "\\" + fileName;

Если вы хотите выйти из своей программы без вызова каких-либо блоков finally или финализаторов, используйте FailFast :

Environment.FailFast()

Я не знаю, скрытая ли это функция (""). Любая строковая функция.

Избегайте проверки нулевых обработчиков событий

Добавление пустого делегата к событиям при объявлении, устранение необходимости всегда проверять событие на наличие null перед его вызовом - это здорово. Пример:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

Позволь тебе сделать это

public void DoSomething()
{
    Click(this, "foo");
}

Вместо этого

public void DoSomething()
{
    // Unnecessary!
    MyClickHandler click = Click;
    if (click != null) // Unnecessary! 
    {
        click(this, "foo");
    }
}

Также см. Это связанное обсуждение и это сообщение в блоге Эрика Липперта по этой теме (и возможные недостатки).

Я склонен обнаруживать, что большинство разработчиков C# не знают о типах, допускающих значение NULL. В основном это примитивы, которые могут иметь нулевое значение.

double? num1 = null; 
double num2 = num1 ?? -100;

Установите значение null для двойного числа num1 , допускающего значение NULL, а затем установите для обычного типа double, num2 , значение num1 или -100, если значение num1 было равно NULL.

http://msdn.microsoft.com/en-us/library/1t3y8s4s(VS.80).aspx

еще одна вещь о типе Nullable:

DateTime? tmp = new DateTime();
tmp = null;
return tmp.ToString();

это return String.Empty. Проверьте эту ссылку для получения более подробной информации

Ключевое слово default в универсальных типах:

T t = default(T);

приводит к 'null', если T является ссылочным типом, и 0, если это int, false, если это логическое значение, и так далее.

Я долгое время не знал ключевого слова "as".

MyClass myObject = (MyClass) obj;

против

MyClass myObject = obj as MyClass;

Второй вернет null, если obj не является MyClass, а не вызовет исключение приведения класса.

Атрибуты в целом, но больше всего DebuggerDisplay . Экономит годы.

Вот полезный для регулярных выражений и путей к файлам:

"c:\\program files\\oldway"
@"c:\program file\newway"

@ Указывает компилятору игнорировать любые escape-символы в строке.

Все остальное плюс

1) неявные дженерики (почему только для методов, а не для классов?)

void GenericMethod<T>( T input ) { ... }

//Infer type, so
GenericMethod<int>(23); //You don't need the <>.
GenericMethod(23);      //Is enough.

2) простые лямбды с одним параметром:

x => x.ToString() //simplify so many calls

3) анонимные типы и инициализаторы:

//Duck-typed: works with any .Add method.
var colours = new Dictionary<string, string> {
    { "red", "#ff0000" },
    { "green", "#00ff00" },
    { "blue", "#0000ff" }
};

int[] arrayOfInt = { 1, 2, 3, 4, 5 };

Другой:

4) Авто свойства могут иметь разный объем:

public int MyId { get; private set; }

Спасибо @pzycoman за напоминание:

5) Псевдонимы пространств имен (не то чтобы вам, вероятно, понадобится это конкретное различие):

using web = System.Web.UI.WebControls;
using win = System.Windows.Forms;

web::Control aWebControl = new web::Control();
win::Control aFormControl = new win::Control();

Не уверен, почему кто-то когда-либо захочет использовать Nullable <bool>. :-)

Верно, неверно , FileNotFound?

@ Указывает компилятору игнорировать любые escape-символы в строке.

Просто хотел прояснить это ... он не говорит ему игнорировать escape-символы, он фактически говорит компилятору интерпретировать строку как литерал.

Если у вас есть

string s = @"cat
             dog
             fish"

на самом деле он будет распечатан как (обратите внимание, что он даже включает пробелы, используемые для отступа):

cat
             dog
             fish

Мне нравятся две вещи: автоматические свойства, поэтому вы можете еще больше свернуть свой код:

private string _name;
public string Name
{
    get
    {
        return _name;
    }
    set
    {
        _name = value;
    }
}

становится

public string Name { get; set;}

Также инициализаторы объектов:

Employee emp = new Employee();
emp.Name = "John Smith";
emp.StartDate = DateTime.Now();

становится

Employee emp = new Employee {Name="John Smith", StartDate=DateTime.Now()}

Из CLR через C# :

При нормализации строк настоятельно рекомендуется использовать ToUpperInvariant вместо ToLowerInvariant, поскольку Microsoft оптимизировала код для выполнения сравнения в верхнем регистре .

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

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

// Useful? probably not.
private void foo()
{
    var user = AnonCast(GetUserTuple(), new { Name = default(string), Badges = default(int) });
    Console.WriteLine("Name: {0} Badges: {1}", user.Name, user.Badges);
}

object GetUserTuple()
{
    return new { Name = "dp", Badges = 5 };
}    

// Using the magic of Type Inference...
static T AnonCast<T>(object obj, T t)
{
   return (T) obj;
}

Использование @ для имен переменных, которые являются ключевыми словами.

var @object = new object();
var @string = "";
var @if = IpsoFacto(); 

От Рика Страла :

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

string result = value1 ?? value2 ?? value3 ?? String.Empty;

лямбды и вывод типов недооцениваются. Лямбда-выражения могут иметь несколько операторов, и они автоматически дублируются как совместимый объект делегата (просто убедитесь, что подпись совпадает), как в:

Console.CancelKeyPress +=
    (sender, e) => {
        Console.WriteLine("CTRL+C detected!\n");
        e.Cancel = true;
    };

Обратите внимание, что у меня нет new CancellationEventHandler и мне не нужно указывать типы sender и e, выводимые из события. Вот почему это менее громоздко для написания всего, delegate (blah blah) что также требует, чтобы вы указали типы параметров.

Лямбда-выражения не должны ничего возвращать, а вывод типа чрезвычайно эффективен в подобном контексте.

И, кстати, вы всегда можете вернуть Lambdas, которые делают Lambdas в смысле функционального программирования. Например, вот лямбда, которая создает лямбда, обрабатывающую событие Button.Click:

Func<int, int, EventHandler> makeHandler =
    (dx, dy) => (sender, e) => {
        var btn = (Button) sender;
        btn.Top += dy;
        btn.Left += dx;
    };

btnUp.Click += makeHandler(0, -1);
btnDown.Click += makeHandler(0, 1);
btnLeft.Click += makeHandler(-1, 0);
btnRight.Click += makeHandler(1, 0);

Обратите внимание на цепочку: (dx, dy) => (sender, e) =>

Вот почему я счастлив пройти курс функционального программирования :-)

Помимо указателей в C, я думаю, это еще одна фундаментальная вещь, которую вам следует изучить :-)

Я думаю, что одна из наиболее недооцененных и менее известных функций C# (.NET 3.5) - это деревья выражений , особенно в сочетании с универсальными шаблонами и лямбдами. Это подход к созданию API, который используют новые библиотеки, такие как NInject и Moq.

Например, предположим, что я хочу зарегистрировать метод в API, и этот API должен получить имя метода.

Учитывая этот класс:

public class MyClass
{
     public void SomeMethod() { /* Do Something */ }
}

Раньше было очень распространено видеть, как разработчики делают это со строками и типами (или чем-то еще, в основном на основе строк):

RegisterMethod(typeof(MyClass), "SomeMethod");

Что ж, это отстой из-за отсутствия строгой типизации. Что, если я переименую "SomeMethod"? Однако теперь, в версии 3.5, я могу сделать это строго типизированным способом:

RegisterMethod<MyClass>(cl => cl.SomeMethod());

В котором класс RegisterMethod использует Expression<Action<T>> следующее:

void RegisterMethod<T>(Expression<Action<T>> action) where T : class
{
    var expression = (action.Body as MethodCallExpression);

    if (expression != null)
    {
        // TODO: Register method
        Console.WriteLine(expression.Method.Name);
    }
}

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

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

private IList<Foo> _foo;

public IList<Foo> ListOfFoo 
    { get { return _foo ?? (_foo = new List<Foo>()); } }

Псевдонимы дженериков:

using ASimpleName = Dictionary<string, Dictionary<string, List<string>>>;

Это позволяет вам использовать ASimpleName вместо Dictionary<string, Dictionary<string, List<string>>> .

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

Союзы (тип разделяемой памяти C++) в чистом и безопасном C#

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

[StructLayout(LayoutKind.Explicit)]
public class A
{
    [FieldOffset(0)]
    public byte One;

    [FieldOffset(1)]
    public byte Two;

    [FieldOffset(2)]
    public byte Three;

    [FieldOffset(3)]
    public byte Four;

    [FieldOffset(0)]
    public int Int32;
}

Вы можете изменять значения байтовых полей, манипулируя полем Int32 и наоборот. Например, эта программа:

    static void Main(string[] args)
    {
        A a = new A { Int32 = int.MaxValue };

        Console.WriteLine(a.Int32);
        Console.WriteLine("{0:X} {1:X} {2:X} {3:X}", a.One, a.Two, a.Three, a.Four);

        a.Four = 0;
        a.Three = 0;
        Console.WriteLine(a.Int32);
    }

Выводит это:

2147483647
FF FF FF 7F
65535

просто добавьте using System.Runtime.InteropServices;

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

public static DeepCopy(this IPrototype p) { ... }

Конечно, приносится в жертву некоторая ясность. Но это работает!