C# and JavaScript


Мой более-менее серьезный путь в программировании начался с написания программ на языке C#, иногда я пробовал писать на JavaScript, и то и дело впадал в ступор в таких ситуациях, когда неверно указывал имя переменной и узнавал об этом спустя много много лет час отладки, так как со мной рядом не было моего компилятора, который бы меня выручил в трудную минуту. Через некоторое время, помимо C# я начал писать много кода на JavaScript и в настоящее время могу делать это без особых трудностей, меня больше не смущает неявное приведение типов и динамическая типизация.


В данной статье я бы хотел систематизировать свои базовые знания об этих языках и рассмотреть их сходства и различия. Данная статья может служить руководством для C# разработчиков, которые хотят изучить JavaScript и наоборот. Также хочу заметить, что в данной статье описываются возможности клиентского JS, так как опыта разработки на Node.js у меня нет. Итак, если вы все ещё не потеряли интерес — приступим.


Namespace и js-модули


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


В C# для разбиения программы на части используются пространства имен. Для их объявления используется ключевое слово namespace. Например, если мы хотим создать набор компонентов пользовательского интерфейса, то логично поместить их все в одно пространство имен, например, Components. При этом принято чтобы пространство имен имело следующее именование [AssemblyName].[DirectoryName].[DirectoryName].[...]. В каждом файле класс компонента пользовательского интерфейса необходимо поместить внутрь пространства имен:


Содержимое файла ComboBox.cs:


namespace AssemblyName.Components
{
    public class ComboBox 
    { 
        // ...
    }
}

Для того, чтобы начать использовать компоненты необходимо импортировать их из пространства имён следующим образом using AssemblyName.Components. При данном способе подключения одной строкой мы импортируем все объекты в текущий файл.


В JS для этих же целей применяются ES-модули. При их использовании мы в какой-то степени эмулируем поведение пространств имен написанием дополнительного кода. Рассмотрим тот же пример с библиотекой компонентов. Допустим у нас есть папка Components, которая содержит компоненты пользовательского интерфейса ComboBox.js, Checkbox.js, Button.js и тд. Для того чтобы получить схожее поведение по сравнению с пространством имен в папке Components необходимо создать файл index.js, который будет содержать следующий код:


export { default as Dialog } from './ComboBox';
export { default as Button } from './Button';
export { default as Checkbox } from './Checkbox';
// ...

Для того, чтобы использовать данные компоненты необходимо импортировать их в текущий файл. Это можно сделать следующим образом: import * as Components from './../Components', после ключевого слова from нам необходимо указать путь к папке, в которой находятся все описанные компоненты.


Способы объявления переменных


Ключевое слово var


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


double pi = 3.14;
User user = new User();
int[] a = new[] { 0, 1, 2 };

Но мы также можем сказать компилятору, что он должен вывести тип самостоятельно из выражения, стоящего после знака присваивания. Это стало возможно благодаря введению в версии C# 3.0 ключевого слова var.


// i - int
var i = 5;

// a - int[]
var a = new[] { 0, 1, 2 };

С помощью var мы можем создавать объекты анонимного типа:


// anon - Анонимный тип только для чтения
var anon = new { Name = "Terry", Age = 34 };
var type = anon.GetType();//"<>f__AnonymousType0`2"

В JavaScript для объявления переменных также можно использовать ключевое слово var, однако, в отличии от C# областью видимости данных переменных будет вся функция или объект window, если переменная была объявлена вне функции.


var a = 5 // область видимости - window

function go() {
  var a = 6 // область видимости - функция go

  // ...
}

Хоть у вас и есть возможность объявлять переменные с помощью var в JavaScript, но сейчас этого делать не рекомендуется, после выхода стандарта ES6 было добавлено ключевое слово let, которое также позволяет объявлять переменные, но его преимуществом заключается в том, что их областью видимости будет являться блок, в котором они объявлены, а не вся функция.


Константы


Как в C#, так и в JavaScript для объявления константного поля используется ключевое слово const. Правда стоит отметить, что понятие константы в данном случае является различным для данных языков.


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


const int c1 = 5;
const int c2 = c1 + 100;
const string c3 = "Константа";
const bool c4 = true;

const User human = null;
const User human = new User(firstName); //недопустимо, ошибка компиляции

В JavaScript значение константы также нельзя изменять, однако нет ограничений, накладываемых на значение как в языке C#, ей можно присваивать любые значения/объекты/массивы. Однако, если в константу присвоен объект, то от изменения защищена сама константа, но не свойства внутри неё:


const c1 = 5;
const c2 = c1 + 100;
const c3 = "Константа";
const c4 = true;

const user = {
  name: "Петя"
};

user.name = "Петя"; // допустимо
user = 5; // нельзя, будет ошибка

Ключевое слово void


Во время написания данной статьи, я экспериментировал в консоли с функциями и по првычке начал описывать функцию как в C# void SomeFunction..., и для меня было большой неожиданностью, когда я узнал, что в JavaScript есть ключевое слово void. Как оказалось void в JavaScript является унарным оператором, который вычисляет значение операнда, затем отбрасывает его и возвращает undefined.


alert("Привет!");        // "Привет!"
alert(void "Привет!");   // undefined

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


В C# void не является оператором, однако по сути имеет схожее значение. Здесь он обозначает отсутствие возвращаемого значения функции:


public void SampleMethod()
{
    // ...
}

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


var t = typeof(void);
t.Name // System.Void

voidв качестве типа может использоваться только в небезопасном контексте при работе с указателями.


 unsafe
{
    void* identifier; //позволяется, но не рекомендуется
}

Ключевое слово new


В JavaScript ключевое слово new является оператором, и используется привычным для многих C-подобных языков образом — для создания объекта.


function Animal() {
    //...
}

const animal = new Animal();

В C# new может использоваться для следующих целей:


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

Первый случай аналогичен применению new в JavaScript.


class Animal
{
    //...
}

var animal = new Animal();

Основные типы данных


В каждом языке имеются типы данных — примитивы, на основе которых строятся другие типы данных, давайте рассмотрим типы данных предоставляемые нам в C# и JavaScript.


Примитивные типы С#:


  • Целочисленный со знаком: sbyte, short, int, long
  • Целочисленный без знака: byte, ushort, uint,ulong
  • Символы Unicode: char
  • Набор символов Unicode: char
  • Числа с плавающей запятой: float, double
  • Десятичный с повышенной точностью: decimal
  • Логическое значение: bool

Базовый классом является Object.


Для JS:


Примитивные типы данных:


  • Число number
  • Строка string
  • Логический тип boolean
  • Специальное значение null
  • Специальное значение undefined
  • symbol

Базовым типом является Object.


После изучения примитивов обоих языков можно придти к следующим выводам:


  • Вместо достаточно большого набора числовых типов в JavaScript имеется единственный тип number;
  • В JavaScript отсутствует тип char, вместо него стоит использовать тип string;
  • В обоих языках базовым типом является Object;
  • Отличительной особенностью JS является то, что null и undefined выделены в отдельные типы, в то время как в C# null — это ключевое слово обозначающее отсутствие значения.
  • В JS присутствует тип symbol, который используется в основном внутри самого стандарта JavaScript, для того чтобы иметь возможность добавлять новый функционал без конфликта с существующей кодовой базой.

Как правило, сейчас все больше приложений, в которых необходимо выполнять обработку данных на клиенте, для чего требуется большая точность в вычислениях. В настоящее время в JavaScript отсутствует встроенная возможность работы с большими числами, однако в недалеком будущем планируется добавить новый тип BigInt. Для решения аналогичных задач в C# имеется класс System.Numerics.BigInteger.


Проверка типа объекта


Проверка типа является достаточной типичной операцией для большинства языков программирования. Основываясь на типе, мы можем выполнять различные действия. Например, рассмотрим пример из жизни: вы слышите звонок в дверь, если к вам пришел пьяный сосед (объект с типом Пьяный Сосед) чтобы занять денег, то вы вряд ли откроете ему дверь, но если за дверью ваш лучший друг (объект с типом лучший друг), то вы не задумываясь впустите его в квартиру. C# и JavaScript также предоставляют средства для проверки типа объектов.


Оператор typeof


Для получения информации о типе как в C#, так и в JavaScript имеется оператор typeof. Давайте рассмотрим принцип его работы в обоих языках:


В С# оператор typeof применяется к типу и возвращает объект класса Type, который содержит всю информацию о типе.


namespace Zoo {
    public class Animal {}
}

Type t = typeof(Animal);

t.Name // 'Animal'
t.FullName // 'Zoo.Animall'
t.GetMethods // Информация о методах
t.GetFields // Информация обо всех полях
// ...

В JS typeof возвращает строку, указывающую тип операнда.


typeof 30 // 'number'
typeof Symbol() // 'symbol'
typeof undefined // 'undefined'

// Объекты
typeof new Animal() // object
typeof null // 'object'
typeof [1,2,3] // 'object'

// Функции
typeof function() {} // 'function';
typeof class C {} // 'function';

В примере выше можно заметить некоторые особенности работы данного оператора. Кажется логичным, если выражение typeof new Animal() возвращало бы строку 'Animal', a typeof [1,2,3] — строку Array, однако как бы ни было парадоксально, результатом в обоих случаях является 'object'. Также в связи с тем, что классы в JS являются оберткой над функциями, то выражение typeof class C {} вернет 'function' вместо 'class'. Ещё одним интересным фактом является то, что выражение typeof null вернёт 'object'. В JavaScript данный оператор имеет большой недостаток: все не примитивные объекты для него на одно лицо, все они имеют один тип object.


Стоит заметить, что в JavaScript typeof можно применять к чему угодно: объектам, функциям, классам и т.д… В C# данный оператор применяется лишь к типам.


is и instanceof


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


В C# для данных целей имеется оператора is.


class Person
{
}

//Наследуем Programmer от Person
class Programmer : Person
{
}

var person = new Person();
var programmer = new Programmer();

person is Person //true
person is Programmer //false
programmer is Person //true
programmer is Programmer //true

В JavaScript для того, чтобы выяснить к какому типу принадлежит объект необходимо использовать оператор — instanceof.


function Person() {}

function Programmer() {}
//Наследуем Programmer от Person
Programmer.prototype = Object.create(Person.prototype);

var person = new Person();
var programmer = new Programmer();

console.log(person instanceof Person); // true
console.log(person instanceof Programmer); // false
console.log(programmer instanceof Person); // true
console.log(programmer instanceof Programmer); // true

Логические значения и проверка на null


Практически повсеместно, для того чтобы не получить Null reference exception, перед использованием переменной мы проверяем её на null, а в случае с JavaScript, ещё и на undefined.


В C# мы постоянно видим подобный код:


if(user != null && String.IsNullOrEmpty(user.name)) {
    user.SetName("Петя");
}

В JavaScript данную конструкцию можно записать несколько короче. Связано это с тем, что в отличии от C#, в JavaScript множество значений кроме false при приведении типов также расцениваются как false:


  1. null
  2. undefined
  3. «» (пустая строка)
  4. 0
  5. NaN (not a number)

Таким образом, приведенный выше код на C# можно записать следующим образом:


if (user && !user.name) {
    user.setName("Петя");
}

или


user && !user.name && user.setName("Петя");

В связи с тем, что проверки на null происходят повсеместно, в C# 6.0 был добавлен Null Propagation Operator .?.


Код на языке C#:


if (user != null && user.parent != null && user.parent.parent != null) {
    user.parent.parent.SetName("Петя");
}

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


user?.parent?.parent?.SetName("Петя");

В JavaScript обычно делают следующим образом:


user && user.parent && user.parent.parent && user.parent.parent.setName("Петя");

Установка значений по-умолчанию


Ещё одной частой операцией является установка значений по-умолчанию, с версии 2.0 в C# появился Null Coalescing Operator — ??.


Следующие две строки кода на C# являются эквивалентными:


var name = user != null && user.name != null ? user.name : "Петя";

var name = user?.name ?? "Петя";

В JavaScript подобную операцию обычно делают следующим образом.


var name = user && user.name || "Петя";

Однако мы можем применять операторы && и || только в том случае, если 0, false и пустая строка не являются допустимыми значениями.


В обозримом будущем операторы ?., ?? должны появиться и в JavaScript (в настоящее время они прошли стадию Stage0), подробнее об этих операторах в JavaScript можно прочитать в статье.


Ключевое слово this


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


В C# ключевое слово this указывает на текущий экземпляр класса.


class User
{
    public string Name { get; set; }

    public void PrintEmployee() {
        Console.WriteLine(this.name);
    }
}

var employee = new Employee();
E1.PrintEmployee();

В данном примере в выражении Console.WriteLine(this.name), this указывает на переменную employee.


Так как this — текущий экземпляр класса, то его нельзя использовать в методах не привязанных к определенному типу, например в статических методах.


В JavaScript значение this называется контекстом вызова и будет определено в момент вызова функции. Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный this:


var user = { firstName: "Петя" };
var admin = { firstName: "Админ" };

function func() {
  alert( this.firstName );
}

user.f = func;
admin.g = func;

// this равен объекту перед точкой:
user.f(); // Петя
admin.g(); // Админ
func();// undefined - в данном случае this - глобальный объект window

К тому же, в JavaScript присутствует возможность явного указания значения this с помощью функций: call, bind, apply. Например, вышеприведенный пример можно переписать следующим образом:


var user = { firstName: "Петя" };
var admin = { firstName: "Админ" };

function func() {
  alert( this.firstName );
}

// this равен объекту перед точкой:
func.call(user); // Петя
func.call(admin); // Админ

func.bind(user)();// Петя
func.bind(admin)();// Админ

Деструктуризация


Часто бывает необходимо присвоить несколько полей объекта локальным переменным. Например, как часто вы наблюдаете подобный код?


void Method(User user)
{
    var firstName = user.FirstName;
    var lastName = user.LastName;
    //...
}

Для подобных целей можно использовать деструктуризацию. Данную возможность в разной степени поддерживают оба языка.


В C# 7.0 для поддержки деструктуризации появился новый вид функций, называемый деконструкторами. Для того чтобы объявить деконструктор нам необходимо определить метод с именем Deconstruct, все параметры которого должны быть объявлены с модификатором out:


class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // Объявление деконструктора
    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = this.FirstName;
        lastName = this.LastName;
    }
}

...

Person person = new Person { FirstName = "Петя", LastName = "Петров" };

(string firstName, string lastName) = person;
(string firstName, _ ) = person;

Поддержка деструктуризации или (destructuring assignment) в JavaScript появилась в шестом стандарте EcmaScript. С её помощь. можно присвоить массив или объект сразу нескольким переменным, разбив его на части.


let [firstName, lastName] = ["Петя", "Петров"];
let [firstName, _ ] = ["Петя", "Петров"];

let { firstName, lastName } = { firstName: "Петя", lastName: "Петров" };
let { firstName } = { firstName: "Петя", lastName: "Петров" };

Стоит отметить, что деструктуризация в JavaScript имеет больше возможностей, чем в C#:


  • Изменение порядка переменных;
  • Отсутствие необходимости явного объявления деконструкторов;
  • Поддержка деструктуризации массивов;
  • Установки значений по-умолчанию;
  • Присваивание свойств объекта в переменную с другим именем;
  • Поддержка вложенной деструктуризации.

Заключение


В данной статье мы обсудили лишь самые основные концепции языков C# и JavaScript. Но остались не затронуты ещё многие аспекты:


  • коллекции
  • функции
  • классы
  • многопоточность

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

Комментарии (30)


  1. gotoxy
    19.06.2018 20:04
    +1

    Я таки понимаю, что движет писать подобные сравнения. Но я таки не понимаю, что движет писать их на хабр.


    1. Siemargl
      19.06.2018 20:33

      Возраст, энтузиазм, и отсутствие академических знаний, не говоря уж об опыте [получения минусов на хабре конкретно]


    1. igrig
      19.06.2018 23:23

      А мне понравился слог автора, мотивацию оставим за рамками. Хотя зачем сравнивать C# и JavaScript, используемые в разных контекстах?


      1. cherkalexander Автор
        20.06.2018 09:15

        Спасибо за комментарий. Замечание вполне корректно. В основном эта статья должна помочь изучить второй язык, именно для этого я описываю сходства и различия. Но C# и JavaScript в настоящее время применяются не совсем в разных контекстах. Например, для написания мобильных приложения может стоять выбор между Xamarin (C#) и React Native(JavaScript). К тому же, сейчас команда Microsoft разрабатывает экспериментальный web framework Blazor на C#, что стало возможно благодаря WebAssembly, так что кто знает, возможно скоро C# может придти и во фронтенд...


      1. arestenkos
        20.06.2018 11:34

        поддерживаю автора.
        я фронтэнд, сейчас руководство хочет, чтоб я плавно переходил в фуллстеки со знанием c#.
        для меня статья — самое оно!


    1. TheLongwalker
      20.06.2018 09:04

      Хочу присоединиться к комментарию. Смысл и ценность этого сравнения и этой статьи для сообщества совершенно непонятны.


      1. cherkalexander Автор
        20.06.2018 09:07

        Если у нас в запасе уже есть изученный язык программирования, то гораздо легче изучать новый на основе того, что нам уже известно. Существует множество статей с названиями вроде Go для JavaScript разработчиков. Данная статья также является некоторым руководством для программистов, которые хотят изучить JS или C#, зная другой язык.


        1. Kanut79
          20.06.2018 12:27
          -1

          Но это два разных по принципу языка. И если кто-то захочет переучиться с С# на скажем scheme, то вы ему тоже такую статью напишите и будете считать что это нормально? А если кто-то зная c# решит SQL изучать?

          Для начала надо не команды, операторы и типы переменных с одинаковыми названиями сравнивать, а понять в чём заключается эта самая принципиальная разница.

          P.S. То есть такую статью для перехода с С# на Java или обратно я бы ещё понял. Но С# и JavaScript?..


          1. Makc_K
            20.06.2018 12:44

            понять в чём заключается эта самая принципиальная разница.

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


          1. cherkalexander Автор
            20.06.2018 13:45

            Возможно вы прав, но я считаю всегда легче изучать что-то новое проводя аналогии из того, что уже известно. Также с каждым новым стандартом JS разница между языками становится всё меньше.


            1. Kanut79
              20.06.2018 14:08

              Тогда начните с того что обьясните разницу между C# и JavaScript. Потому что на моей памяти каждый раз когда кого-то вот так «переучивали» с C# на JavaScript это всегда приводило к проблемам в понимании и косякам в коде.


            1. Makc_K
              20.06.2018 16:09

              Принципиальная разница как была, так и будет. Компилируемый vs интерпретируемый. Строгая типизация vs слабая типизация.


        1. DeadKnight
          20.06.2018 20:44

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

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

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


          1. cherkalexander Автор
            20.06.2018 20:58

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


  1. EvilGenius18
    19.06.2018 20:34

    В настоящее время в JavaScript отсутствует возможность работы с большими числами
    Это утверждение не совсем корректно. Уже есть множество JS модулей, позволяющих работать с bigInt


    1. napa3um
      19.06.2018 22:30

      А также уже есть встроенная реализация BigInt (доступна в Хроме 67, например).


    1. cherkalexander Автор
      20.06.2018 08:59

      Да, конечно, существует множество пакетов, но здесь я подразумевал именно нативную поддержку BigInt. Конечно в новейшей версии хрома она уже реализуется, но при разработке приложения нам необходимо поддерживать и более старые браузеры, так что можно сказать, что нативной поддержки всё ещё нет. При том, что спецификация ещё находится в разработке и может изменяться, на мой взгляд, пока надежнее использовать сторонние пакеты.


  1. DeadKnight
    19.06.2018 23:50

    Сравнение МASM и Visual Basic ждать стоит?


    1. cherkalexander Автор
      20.06.2018 08:58

      Я не изучал ни MASM, ни Visual Basic, и не вижу в этом практического смысла.


    1. 0xd34df00d
      20.06.2018 16:44

      А также sql и php, sh и haskell, c++ и malbolge.


  1. kuftachev
    20.06.2018 01:58

    У человека летние каникулы, решил вот статью написать — это лучше чем пиво в гаражах пить.


  1. Kaktusbot
    20.06.2018 08:45

    Я вроде студент первого курса который с шарпом тока познакомился.
    Но разве ж оператор ?? в шестом шарпе появился? Намного раньше же, не знаю когда правда.

    if(user != null && String.IsNullOrEmpty(user.name))

    А учитывая шестой шарп можно как раз вроде написать
    if(String.IsNullOrEmpty(user?.name))

    Проверять лень ибо студия полдня запускается.


  1. cherkalexander Автор
    20.06.2018 08:52

    Насколько я помню, ?? оператор всё таки появился в C# 6.0. В данной ссылке есть упоминания о нем.


    1. Kaktusbot
      20.06.2018 09:24

      В статье на вики оператор ?? указан среди новшеств шарпика второго, 2005 год.
      То что есть упоминания о нём среди новшеств шестой версии ничего не значит.


      1. cherkalexander Автор
        20.06.2018 09:28

        Возможно я ошибаюсь, можете дать ссылку на вики?


        1. Kaktusbot
          20.06.2018 09:33

          ru.wikipedia.org/wiki/C_Sharp#Версии
          Только долистайте до развернутого описания версий, а не в таблички смотрите.


          1. cherkalexander Автор
            20.06.2018 09:37

            Большое спасибо! Поправил.


  1. Makc_K
    20.06.2018 09:14

    А ещё в c# есть тип dynamic. Который позволяет запихнуть в себя вообще всё, что угодно. Из-за этого код некоторых «программистов» превращается в треш, угар и содомию.


  1. LekaOleg
    20.06.2018 17:24

    Спасибо, статься отличная! Буду ждать продолжения. Но хотелось бы ещё чуть больше не сложных примеров) Особенно по С#, так как сам js знаю на хорошем уровне, а так же php. И хотелось бы на базовом уровне изучить С# (+м было бы ещё с TypeScript), а ещё круто было бы сравнение с Java).


    1. cherkalexander Автор
      20.06.2018 21:11

      Спасибо за комментарий! А для каких целей вам необходимо изучить C#? С какими трудностями вы встречаетесь при написании кода на C# после JS?