Мой более-менее серьезный путь в программировании начался с написания программ на языке 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
:
null
undefined
- «» (пустая строка)
0
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)
EvilGenius18
19.06.2018 20:34В настоящее время в JavaScript отсутствует возможность работы с большими числами
Это утверждение не совсем корректно. Уже есть множество JS модулей, позволяющих работать с bigInt
napa3um
19.06.2018 22:30А также уже есть встроенная реализация BigInt (доступна в Хроме 67, например).
cherkalexander Автор
20.06.2018 08:59Да, конечно, существует множество пакетов, но здесь я подразумевал именно нативную поддержку
BigInt
. Конечно в новейшей версии хрома она уже реализуется, но при разработке приложения нам необходимо поддерживать и более старые браузеры, так что можно сказать, что нативной поддержки всё ещё нет. При том, что спецификация ещё находится в разработке и может изменяться, на мой взгляд, пока надежнее использовать сторонние пакеты.
DeadKnight
19.06.2018 23:50Сравнение МASM и Visual Basic ждать стоит?
cherkalexander Автор
20.06.2018 08:58Я не изучал ни MASM, ни Visual Basic, и не вижу в этом практического смысла.
kuftachev
20.06.2018 01:58У человека летние каникулы, решил вот статью написать — это лучше чем пиво в гаражах пить.
Kaktusbot
20.06.2018 08:45Я вроде студент первого курса который с шарпом тока познакомился.
Но разве ж оператор??
в шестом шарпе появился? Намного раньше же, не знаю когда правда.
if(user != null && String.IsNullOrEmpty(user.name))
А учитывая шестой шарп можно как раз вроде написать
if(String.IsNullOrEmpty(user?.name))
Проверять лень ибо студия полдня запускается.
cherkalexander Автор
20.06.2018 08:52Насколько я помню,
??
оператор всё таки появился в C# 6.0. В данной ссылке есть упоминания о нем.Kaktusbot
20.06.2018 09:24В статье на вики оператор ?? указан среди новшеств шарпика второго, 2005 год.
То что есть упоминания о нём среди новшеств шестой версии ничего не значит.cherkalexander Автор
20.06.2018 09:28Возможно я ошибаюсь, можете дать ссылку на вики?
Kaktusbot
20.06.2018 09:33ru.wikipedia.org/wiki/C_Sharp#Версии
Только долистайте до развернутого описания версий, а не в таблички смотрите.
Makc_K
20.06.2018 09:14А ещё в c# есть тип dynamic. Который позволяет запихнуть в себя вообще всё, что угодно. Из-за этого код некоторых «программистов» превращается в треш, угар и содомию.
LekaOleg
20.06.2018 17:24Спасибо, статься отличная! Буду ждать продолжения. Но хотелось бы ещё чуть больше не сложных примеров) Особенно по С#, так как сам js знаю на хорошем уровне, а так же php. И хотелось бы на базовом уровне изучить С# (+м было бы ещё с TypeScript), а ещё круто было бы сравнение с Java).
cherkalexander Автор
20.06.2018 21:11Спасибо за комментарий! А для каких целей вам необходимо изучить C#? С какими трудностями вы встречаетесь при написании кода на C# после JS?
gotoxy
Я таки понимаю, что движет писать подобные сравнения. Но я таки не понимаю, что движет писать их на хабр.
Siemargl
Возраст, энтузиазм, и отсутствие академических знаний, не говоря уж об опыте [получения минусов на хабре конкретно]
igrig
А мне понравился слог автора, мотивацию оставим за рамками. Хотя зачем сравнивать C# и JavaScript, используемые в разных контекстах?
cherkalexander Автор
Спасибо за комментарий. Замечание вполне корректно. В основном эта статья должна помочь изучить второй язык, именно для этого я описываю сходства и различия. Но C# и JavaScript в настоящее время применяются не совсем в разных контекстах. Например, для написания мобильных приложения может стоять выбор между Xamarin (C#) и React Native(JavaScript). К тому же, сейчас команда Microsoft разрабатывает экспериментальный web framework Blazor на C#, что стало возможно благодаря WebAssembly, так что кто знает, возможно скоро C# может придти и во фронтенд...
arestenkos
поддерживаю автора.
я фронтэнд, сейчас руководство хочет, чтоб я плавно переходил в фуллстеки со знанием c#.
для меня статья — самое оно!
TheLongwalker
Хочу присоединиться к комментарию. Смысл и ценность этого сравнения и этой статьи для сообщества совершенно непонятны.
cherkalexander Автор
Если у нас в запасе уже есть изученный язык программирования, то гораздо легче изучать новый на основе того, что нам уже известно. Существует множество статей с названиями вроде Go для JavaScript разработчиков. Данная статья также является некоторым руководством для программистов, которые хотят изучить JS или C#, зная другой язык.
Kanut79
Но это два разных по принципу языка. И если кто-то захочет переучиться с С# на скажем scheme, то вы ему тоже такую статью напишите и будете считать что это нормально? А если кто-то зная c# решит SQL изучать?
Для начала надо не команды, операторы и типы переменных с одинаковыми названиями сравнивать, а понять в чём заключается эта самая принципиальная разница.
P.S. То есть такую статью для перехода с С# на Java или обратно я бы ещё понял. Но С# и JavaScript?..
Makc_K
С появлением таких штук, как Node.js эта разница становится меньше. Но в общем с вашим комментарием согласен.
Было бы гораздо интересней, если б автор проанализировал различия между JS и Питоном.
cherkalexander Автор
Возможно вы прав, но я считаю всегда легче изучать что-то новое проводя аналогии из того, что уже известно. Также с каждым новым стандартом JS разница между языками становится всё меньше.
Kanut79
Тогда начните с того что обьясните разницу между C# и JavaScript. Потому что на моей памяти каждый раз когда кого-то вот так «переучивали» с C# на JavaScript это всегда приводило к проблемам в понимании и косякам в коде.
Makc_K
Принципиальная разница как была, так и будет. Компилируемый vs интерпретируемый. Строгая типизация vs слабая типизация.
DeadKnight
Проблема в том, что язык программирования, это немного больше чем названия команд и зарезервированных слов.
Язык программирования включает в себя большой набор прадигм, правил хорошего стиля кода и т.д. и т.п.
Показав человеку, что в двух ЯП, используемых как правило в различных областях, есть ключевые слова с подобными названиями, это как сравнивать русский и английский языки, рассказывая, что в них есть слова и буквы с одинаковым звучанием, и надеясь что знание русского языка, на этом основании, станет некоторым руководством для людей, которые хотят изучить английский язык, зная русский.
cherkalexander Автор
Да, конечно, вы правы. Но статья потому и имеет название основы, потому что здесь описываются только указанные базовые команды и принципы.