Mockowanie - interfejs vs virtual

Mockowanie - interfejs vs virtual
N0
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Gdańsk
  • Postów:647
0

Kiedy powinno się utworzyć interfejs do klasy a kiedy oznaczyć metody jako wirtualne w celu umożliwienia mockowania?

john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
1

Jak ze wszystkim - to zależy. W praktyce osobiście stosuję metody wirtualne tylko jeśli z jakiegoś powodu nie mogę ekstrahować interfejsu.

N0
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Gdańsk
  • Postów:647
0

@john_klamka: Ale dlaczego? Chodzi o wydajność metod wirtualnych? O ile się nie myle, to @Wibowit pisał kiedyś, że metody wirtualne obniżają wydajność.

WeiXiao
  • Rejestracja:około 9 lat
  • Ostatnio:około 11 godzin
  • Postów:5108
1
nobody01 napisał(a):

@john_klamka: Ale dlaczego? Chodzi o wydajność metod wirtualnych? O ile się nie myle, to @Wibowit pisał kiedyś, że metody wirtualne obniżają wydajność.

Wydajność? a to nie są jakieś tam nanosekundy per setki milionów wywołań?

Interface Performance. Are interface calls faster than virtual ones?

Według mnie metody wirtualne = wchodzimy głębiej w OOP = zaczynają się problemy i wypadałoby przemyśleć swoje życie :D

Should I use virtual methods to make mocking possible?

How would virtual methods help? The idea of mocking is that you rip out a class completely from your application and plug in a completely different mocked class, with the only thing in common that they both implement the same interface. Inheritance doesn't come into the game at all.


Virtual methods and overriding them is not a good way to make mocks compared to interfaces.
Because you will have to reference the underlying class to create your mock. So your tests will be reliant on a reference to that particular library
And also because you will expose methods that you otherwise might not want to.


If youre needing to make your methods virtual in order to test them (i.e. youre expanding the public API of your class purely to accommodate your unit tests), then its a good sign that your class has too much work to do.

edytowany 9x, ostatnio: WeiXiao
mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
2

Jeśli mockujesz klasę z metodami wirtualnymi to wciąż uruchamiasz jej konstruktor, więc jeśli masz tam np. jakąś walidację to wymusza na Tobie zbudowanie poprawnego obiektu do testów, chociaż niekoniecznie to ma sens.

N0
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Gdańsk
  • Postów:647
0

@mad_penguin: Nie wystarczy po prostu utworzyć bezparametrowy konstruktor?

edytowany 1x, ostatnio: nobody01
mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
2

Jeśli nie masz żadnego konstruktora to automatycznie masz bezparametrowy, a jeśli masz już jakiś inny, to tworzenie konstruktora, który nie konstruuje poprawnego obiektu specjalnie do testów jest złym pomysłem, bo ktoś go może prędzej czy później użyć poza testami :)
Swoją drogą moje doświadczenie z testami jest niewielkie, więc to tylko moje 3gr.

N0
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Gdańsk
  • Postów:647
0

Właściwie to chodzi mi o to, że ludzie piszą, aby nie pisać niepotrzebnych interfejsów, nie bać się własnego kodu itd. Ale z drugiej strony, preferowanym podejściem przy mockowaniu wydaje się być właśnie tworzenie interfejsów.

john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
1

Nie zdażyło mi się jeszcze spotkać sytuacji, w której metody wirtualne powodowałyby problemy wydajnościowe - ale z kolei mało w życiu widziałem. Bardziej chodziło mi o to, że interfejs jest zwyczajnie wygodniejszy w mockowaniu - nie masz, tak jak wyżej zostało napisane, żadnego konstruktora, w którym może być wszystko ("tu żyją smoki" ;)).

Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
0

Właściwie to chodzi mi o to, że ludzie piszą, aby nie pisać niepotrzebnych interfejsów

Kto pisze i w jakim kontekście? Jeśli chodzi o wydajność to nie ma to znaczenia, a jeśli będzie miało to sam będziesz o tym wiedział.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 1x, ostatnio: Aventus
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0
nobody01 napisał(a):

@john_klamka: Ale dlaczego? Chodzi o wydajność metod wirtualnych? O ile się nie myle, to @Wibowit pisał kiedyś, że metody wirtualne obniżają wydajność.

W starym .NETu w zasadzie wszystko obniża wydajność. Interfejsy, metody wirtualne, tworzenie krótko żyjących obiektów, itd .NET Core ma już wbudowaną dewirtualizację. Nie testowałem jej, ale możliwe, że w końcu .NET doścignął Javę 6u24 z 2011 roku.

Okazuje się, że wszystkie metody z intefejsów są wirtualne:
https://stackoverflow.com/a/3621456

Quoting Jeffrey Ritcher from CLR via CSharp 3rd Edition here
The CLR requires that interface methods be marked as virtual. If you do not explicitly mark the method as virtual in your source code, the compiler marks the method as virtual and sealed; this prevents a derived class from overriding the interface method. If you explicitly mark the method as virtual, the compiler marks the method as virtual (and leaves it unsealed); this allows a derived class to override the interface method. If an interface method is sealed, a derived class cannot override the method. However, a derived class can re-inherit the same interface and can provide its own implementation for the interface’s methods.

Stary .NET miał jakieś mikrooptymalizacje dla domyślnych typów metod w interfejsach (virtual + sealed), więc ekstrahowanie interfejsów dla zysków w wydajności mogło się przerodzić w cargo cult.

Bardziej chodziło mi o to, że interfejs jest zwyczajnie wygodniejszy w mockowaniu - nie masz, tak jak wyżej zostało napisane, żadnego konstruktora, w którym może być wszystko ("tu żyją smoki" ;)).

Konstruktor z efektami ubocznymi to w ogólności rak. Zamiast przykrywać problem tworząc zbędny interfejs można przenieść efekty uboczne do metody fabrykującej. Ale co kto lubi :)

Polecam lekturę tego: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
0

Konstruktor z efektami ubocznymi to w ogólności rak. Zamiast przykrywać problem tworząc zbędny interfejs można przenieść efekty uboczne do metody fabrykującej. Ale co kto lubi :)

Sugerujesz, ze lubię raka? Nie zawsze istnieje możliwość dowolnej modyfikacji zastanego kodu (np. gdy chcesz zamockować moduł pisany przez inny team w innym projekcie w innej organizacji na innym kontynencie).

n0name_l
Wtedy się pisze klasę, która deleguje wywołania do zewnętrznego modułu i dorabia do niej interfejs, żeby to można było zamockować.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

Sugerujesz, ze lubię raka? Nie zawsze istnieje możliwość dowolnej modyfikacji zastanego kodu (np. gdy chcesz zamockować moduł pisany przez inny team w innym projekcie w innej organizacji na innym kontynencie).

Jeśli nie masz kontroli nad klasą to tym bardziej nie dorzucisz jej interfejsu.

How would virtual methods help? The idea of mocking is that you rip out a class completely from your application and plug in a completely different mocked class, with the only thing in common that they both implement the same interface. Inheritance doesn't come into the game at all.

Virtual methods and overriding them is not a good way to make mocks compared to interfaces.
Because you will have to reference the underlying class to create your mock. So your tests will be reliant on a reference to that particular library
And also because you will expose methods that you otherwise might not want to.

If youre needing to make your methods virtual in order to test them (i.e. youre expanding the public API of your class purely to accommodate your unit tests), then its a good sign that your class has too much work to do.

Pitu pitu. Jak to się ma do robienia ekstra interfejsu na wzór zewnętrznej klasy + ekstra wewnętrznej klasy która jedynie deleguje do zewnętrznej? Gdzie tu jest "too much work" skoro jedynym zadaniem wewnętrznej klasy jest delegowanie do innej? Pamiętaj, że każda metoda z intefejsu jest wirtualna.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
john_klamka
"Jeśli nie masz kontroli nad klasą to tym bardziej nie dorzucisz jej interfejsu" to też jest takie czysto teoretyczne pitu pitu, przykłady i kontrprzykłady można mnożyć w nieskończoność...
Wibowit
A jesteś w stanie? Pokaż mi np jak dodasz swój intefejs do stringa.
john_klamka
lol, nie ma to jak przykład z d**y :D
Wibowit
Dobra, zamiast zachowywać się jak dzieciak może zrobiłbyś to co sam zasugerowałeś?
john_klamka
zachowam się jak dzieciak i pójdę się bawić gdzie indziej
n0name_l
  • Rejestracja:ponad 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:2412
1

Interfejsów się w gruncie rzeczy używa najczęściej w 2 sytuacjach:

  1. Jako kontrakt użycia zewnętrznego modułu,
  2. Jeśli planujemy wiele implementacji.

A metod wirtualnych z kolei w zupełnie innych przypadkach.

Większości rzeczy nie musisz mockować, nie ma za bardzo sensu tego robić, a jeśli już musisz to użyj interfejsu.

Wibowit
Jako kontrakt użycia zewnętrznego modułu, - czyli to co opisałem w poprzednim poście: dodatkowy interfejs + dodatkowa klasa (wrapper delegujący do zewnętrznej klasy) zamiast samej dodatkowej klasy (tego wrappera).
n0name_l
No tak, jakoś czasowo się średnio zgrało, bo jak pisałem to nie widziałem tego. :P
Wibowit
No bo w sumie zedytowałem posta później :P Ale komentarz zostawiłem, żeby rozwiać wątpliwości.
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Wrocław
5
nobody01 napisał(a):

Kiedy powinno się utworzyć interfejs do klasy

Wtedy, gdy klasa jest elementem jakiegoś wzorca projektowego bazującego na wspólnym interfejsie (np. strategia albo chain of responsibility) albo służy do komunikacji ze światem zewnętrznym (dostęp do bazy danych, czytanie z pliku, pobieranie danych z webserwisu, itp.).

a kiedy oznaczyć metody jako wirtualne w celu umożliwienia mockowania?

Wtedy gdy:

  1. chce się testować kod napisany specjalnie dla potrzeb testów zamiast faktycznego;
  2. lubi się naprawiać dziwne bugi, których testy nie pokryły;
  3. pisze ogromne klasy łamiące SRP;
  4. uważa się za dobry pomysł zmianę kodu produkcyjnego tylko po to, aby spełnić jakieś ograniczenia narzędzi testowych.
  5. jest się programistą Javy, który nie rozumie, że C# jest inaczej skonstruowany, i próbuje w nim emulować cechy Javy w celu sprowadzenia na siebie problemów z punktów wyżej.

Celem virtual jest umożliwienie polimorficznych wywołań, a nie testowania!

john_klamka napisał(a):

Jak ze wszystkim - to zależy. W praktyce osobiście stosuję metody wirtualne tylko jeśli z jakiegoś powodu nie mogę ekstrahować interfejsu.

Nie istnieją takie przypadki, że się nie da. Co najwyżej nie da się jednym kliknięciem, i trzeba napisać kawałek kodu, który jakoś opakuje ten nieekstrahowalny twór, albo w ogóle trzeba się wreszcie zastanowić nad architekturą, bądź sensem testowania.

nobody01 napisał(a):

Właściwie to chodzi mi o to, że ludzie piszą, aby nie pisać niepotrzebnych interfejsów, nie bać się własnego kodu itd. Ale z drugiej strony, preferowanym podejściem przy mockowaniu wydaje się być właśnie tworzenie interfejsów.

Ale to się w niczym nie kłóci. Po prostu twórz interfejsy tylko do tych klas, które jest sens mockować, a reszty nie mockuj.

john_klamka
To istnieją czy nie istnieją? Bo sam sobie przeczysz. A pisanie o zastanowieniu się nad architekturą to już w ogóle truizm, równie dobrze można się zastanowić nad sensem istnienia.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

Ja jeszcze tylko pokażę, że wyodrębnienie interfejsu na potrzeby testowania (co za dziwny pomysł) wcale nie zabezpiecza przed nadpisywaniem metod, gdyż metody w interfejsach są wirtualne. Przykładzik:

Kopiuj
using System;
 
public class Test
{
	interface Interfejs {
		void metodaWirtualna();
	}
 
	class Klasa1 : Interfejs {
		public virtual void metodaWirtualna() {
			Console.WriteLine("Klasa1");
		}
	}
 
	class Klasa2 : Klasa1 {
		public override void metodaWirtualna() {
			Console.WriteLine("Klasa2");
		}
	}
 
	static void Wypisz(Interfejs obiekt) {
		obiekt.metodaWirtualna();
	}
 
	public static void Main()
	{
		Wypisz(new Klasa1());
		Wypisz(new Klasa2());
	}
}

https://www.ideone.com/fJIHXH
Wypisuje:

Kopiuj
Klasa1
Klasa2

Dopiero jeśli ktoś nie da virtual przy metodzie w klasie to zabezpiecza przed jej nadpisywaniem (bez virtual metoda nadal będzie wirtualna, ale kompilator automatycznie dopisze sealed). Podobny efekt można uzyskać w Javie dopisując do metody słówko final.

Mała poprawka: w C# istnieje coś takiego jak przesłanianie metod!!!

Kopiuj
public class A
{
   public virtual void One();
   public void Two();
}

public class B : A
{
   public override void One();
   public new void Two();
}

Powyższa konstrukcja wygrywa konkurs na największego raka :] Nie jestem w stanie pojąć co autor miał na myśli dodając taką funkcjonalność do języka C#.

PS: Popsutą hierarchię dziedziczenia zawsze można zastąpić popsutą hierarchią kompozycji (delegacji).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 6x, ostatnio: Wibowit
ZK
  • Rejestracja:prawie 7 lat
  • Ostatnio:5 miesięcy
  • Postów:273
0

Chciałbym wiedzieć jak to wszystko pod maską działa co obniża wydajność. Interfejs to jest jakaś funkcjonalność , którą mogą posiadać różne klasy i struktury.
We frameworku masz przykłady interfejsów. A metody wirtualne to są metody które posiadają implementacje i można ją nadpisywać w klasach pochodnych .
Gdzieś kiedyś czytałem ale nie pamiętam już w której książce , że jak się zapieczętuje taką metodę to coś działa szybciej ( override sealed )

edytowany 1x, ostatnio: Zimny Krawiec
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

Interfejs to jest jakaś funkcjonalność , którą mogą posiadają różne klasy i struktury. We frameworku masz przykłady interfejsów. A metody wirtualne to są metody które posiadają implementacje i można ją nadpisywać w klasach pochodnych .

Wszystkie metody w interfejsach są wirtualne i możesz je nadpisywać dowolną ilość razy w hierarchii klas pochodnych. Pokazałem to przecież post wyżej.

Gdzieś kiedyś czytałem ale nie pamiętam już w której książce , że jak się zapieczętuje taką metodę to coś działa szybciej ( override sealed )

To, że coś 10 lat temu wolno działało nie znaczy, że wolno działa dzisiaj. W .NET Core MS w końcu implementuje dewirtualizację, escape analysis i inne optymalizacje:
https://www.infoq.com/news/2017/12/Devirtualization
https://github.com/dotnet/coreclr/issues/9908

W Javie dla przykładu od zawsze gettery i settery są metodami wirtualnymi niefinalnymi (czyil dopuszczającymi nadpisywanie) i odkąd pamiętam nie miało to żadnego wpływu na wydajność. Taka sama szybkość była przy stosowaniu getterów i setterów jak i przy bezpośrednim operowaniu na polach klas. .NET też powoli dochodzi do takiego stanu (w sensie wydajności, nie stylu programowania).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 5x, ostatnio: Wibowit
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Wrocław
2
Wibowit napisał(a):

Ja jeszcze tylko pokażę, że wyodrębnienie interfejsu na potrzeby testowania (co za dziwny pomysł) wcale nie zabezpiecza przed nadpisywaniem metod, gdyż metody w interfejsach są wirtualne.

O to chodzi, że metody interfejsów są wirtualne, bo dzięki tamu można w ogóle mockować, a tego przecież dotyczy ten wątek.

Mała poprawka: w C# istnieje coś takiego jak przesłanianie metod!!!

Są też wskaźniki, typy ze znakiem i wiele innych równie ekscytujących konstrukcji!!!

Powyższa konstrukcja wygrywa konkurs na największego raka :] Nie jestem w stanie pojąć co autor miał na myśli dodając taką funkcjonalność do języka C#.

To jest możliwość dana przez twórców języka programistom. Ma swoje potencjalne zastosowanie, ale raczej nie korzysta się z tego mechanizmu często.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

Są też wskaźniki

No wypas. Wracamy do wielodniowych sesyjek debugowania? Z tym mi się kojarzą wskaźniki. Biorąc pod uwagę rozkminy czy szybszy jest foreach czy for na indeksach to nie zdziwiłbym się, gdyby ktoś w biznesowym kodzie C# zaczął używać wskaźników. Każda pomoc VMce na wagę złota.

W C# 8 Microsoft jedzie po bandzie i implementuje metody domyślne w interfejsach, żywcem wzięte z Javy: https://www.infoq.com/articles/default-interface-methods-cs8 Jest jednak pewien smaczek: można zrobić hierarchię dziedziczenia interfejsów z zawartą w nich dowolną ilością nadpisywania metod wirtualnych, a to wszystko to bez użycia słówek virtual czy override. Na 100% nie wiadomo czy DIM wejdą do C#8, ale szansa jest bardzo wysoka (nie ma sygnałów, by MS się miał wycofać).

Domyślne metody w interfejsach (DIM - default interface methods) mają pewne zalety w porównaniu do metod rozszerzających (EM - extension methods), gdy chcemy rozszerzyć funkcjonalność już istniejących interfejsów:

  • DIM nie wymagają importowania czegokolwiek by używać nowych metod na instancjach danego interfejsu,
  • DIM są wirtualne, a więc np konkretne klasy (np moja własna implementacja listy) czy podinterfejsy mogą nadpisywać domyślną implementację czymś zoptymalizowanym,

"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
WeiXiao
Chcesz powiedzieć że Java to takie środowisko testowe featuresów które potencjalnie* wejdą do C#? :D
Aryman1983
Aryman1983
@WeiXiao: już nie chciałem pisać, ale java chyba dalej nie ma string interpolation :-) Chyba dalej jest String.format() :-)
WeiXiao
@Aryman1983: Może w przyszłości będzie :) https://openjdk.java.net/jeps/326
somekind
@WeiXiao: myślisz, że słabe typowanie też przeniosą z Javy do C#?
WeiXiao
@somekind: a jakie to cuda można robić w javie?
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:2 dni
  • Lokalizacja:Kraków
  • Postów:1114
0
Wibowit napisał(a):

Wszystkie metody w interfejsach są wirtualne i możesz je nadpisywać dowolną ilość razy w hierarchii klas pochodnych. Pokazałem to przecież post wyżej.

A ja chciałbym tylko zauważyć że to że w CIL metody zaimplementowane z interfejsów dostają modyfikatory virtual abstract czy final virtual jest wyłącznie szczegółem implantacyjnym. Z punktu widzenia języka dopóki w naszej klasie/strukturze metody nie dostaną modyfikatora virtual, to zachowują się dokładnie tak samo jak metody nie wirtualne. I też je można dowolną ilość razy przesłaniać za pomocą opcjonalnego modyfikatora "new".

somekind napisał(a):

O to chodzi, że metody interfejsów są wirtualne, bo dzięki tamu można w ogóle mockować, a tego przecież dotyczy ten wątek.

Więc twierdzenie że metody zaimplementowane z interfejsów są wirtualne jest sporym nadużyciem, dopiero jak użyjemy modyfikatora virtual to stają się one wirtualne.
Interfejsy są preferowane przez biblioteki tworzące mocki właśnie dlatego że tworzą dynamicznie typ który implementuje interfejs i żadnej potrzeby używania virtulanych metod nie ma, bo obiekt utworzony (mock) jest pierwszą implementacją w hierarchii dziedziczenia.

Metody wirtualne są/były wolniejsze głównie przez to że nie są inlinowane (używanie v-table do ustalenia z której klasy wywołać metody ma mniejszy narzut), na dodatek odwołanie się przez interfejs do obiektu typu wartościowego powoduje boxing (boxingu można uniknąć wywołując metodę bezpośrednio z typu). Modyfikator sealed w żaden sposób nie jest obecnie brany pod uwagę przez kompilator w kwestii metod virtualnych.
Tak jak zauważył Wibowit, dopiero trwają pracę nad wprowadzeniem dewirtualizacji i ona ma poradzić sobie z powyższymi problemami.

Natomiast jeśli chodzi o metody implantowane z interfejsu, dopóki nie dostaną modyfikatora virtual, to mogę zostać inlinowane, co po raz kolejny pokazuje że blizej im do zwykłych metod niz virtualnych ;)


somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Wrocław
1
Wibowit napisał(a):

No wypas. Wracamy do wielodniowych sesyjek debugowania? Z tym mi się kojarzą wskaźniki. Biorąc pod uwagę rozkminy czy szybszy jest foreach czy for na indeksach to nie zdziwiłbym się, gdyby ktoś w biznesowym kodzie C# zaczął używać wskaźników. Każda pomoc VMce na wagę złota.

Nie każdy ficzer języka ma zastosowanie w każdym rodzaju aplikacji. Dobry programista wie, które mechanizmy są pomocne w danym problemie, a które nie.

neves napisał(a):

Więc twierdzenie że metody zaimplementowane z interfejsów są wirtualne jest sporym nadużyciem, dopiero jak użyjemy modyfikatora virtual to stają się one wirtualne.
Interfejsy są preferowane przez biblioteki tworzące mocki właśnie dlatego że tworzą dynamicznie typ który implementuje interfejs i żadnej potrzeby używania virtulanych metod nie ma, bo obiekt utworzony (mock) jest pierwszą implementacją w hierarchii dziedziczenia.

Masz rację. Ja po prostu dostosowałem język do dyskusji, bo nie podejmuję się tłumaczenia Javowcowi, że w biegu można utworzyć dynamicznie typ. Jeszcze by we mnie rzucił dzidą czy jakimś innym bumerangiem.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

@WeiXiao: myślisz, że słabe typowanie też przeniosą z Javy do C#? - somekind 52 minuty temu

Cały czas nie jestem w stanie się domyślić o co chodzi z tym słabym typowaniem.

@neves:
Wytłumacz mi w takim razie jak działa poniższy kod, a konkretnie linjka z komentarzem:

Kopiuj
using System;
 
public class Test
{
	interface Interfejs {
		void metodaWirtualna();
	}
 
	class Klasa1 : Interfejs {
		public void metodaWirtualna() {
			Console.WriteLine("Klasa1");
		}
	}
 
	class Klasa2 : Interfejs {
		public virtual void metodaWirtualna() {
			Console.WriteLine("Klasa2");
		}
	}
 
	class Klasa3 : Klasa2 {
		public override void metodaWirtualna() {
			Console.WriteLine("Klasa3");
		}
	}
 
	static void Wypisz(Interfejs obiekt) {
		obiekt.metodaWirtualna(); // wirtualna czy nie wirtualna, oto jest pytanie!
	}
 
	public static void Main()
	{
		Wypisz(new Klasa1());
		Wypisz(new Klasa2());
		Wypisz(new Klasa3());
	}
}

https://www.ideone.com/Pb1IyN
Wynik:

Kopiuj
Klasa1
Klasa2
Klasa3

Wrzuciłem ten kod w https://sharplab.io/ i pokazał takie mniej więcej takie coś:

Kopiuj
    .method private hidebysig static 
        void Wypisz (
            class Test/Interfejs obiekt
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: callvirt instance void Test/Interfejs::metodaWirtualna()
        IL_0006: ret
    } // end of method Test::Wypisz

Jak byk mamy callvirt.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:2 dni
  • Lokalizacja:Kraków
  • Postów:1114
1

To że w CIL metoda jest virtualna nie oznacza że w definicji C# też jest virtualna, to jest szczegół implementacyjny.
Tu masz dwa dowody, w obu wypadkach są wyniki Klasa1, Klasa1, a to oznacza że to nie są na poziomie języka metody virtualne, nie ma polimorfizmu.
Więc jeszcze raz w CIL są to metody virtualne, ale nie w języku C#.

Kopiuj
using System;

public class Test
{
    interface Interfejs
    {
        void metodaWirtualna();
    }

    class Klasa1 :Interfejs
    {
        public void metodaWirtualna()
        {
            Console.WriteLine("Klasa1");
        }
    }

    class Klasa2 :Klasa1, Interfejs
    {
        public  void metodaWirtualna()
        {
            Console.WriteLine("Klasa2");
        }
    }

   

    static void Wypisz(Klasa1 obiekt)
    {
        obiekt.metodaWirtualna(); // wirtualna czy nie wirtualna, oto jest pytanie!
    }

    public static void Main()
    {
        Wypisz(new Klasa1());
        Wypisz(new Klasa2());        
    }
}
Kopiuj
using System;

public class Test
{
    interface Interfejs
    {
        void metodaWirtualna();
    }

    class Klasa1 :Interfejs
    {
        public void metodaWirtualna()
        {
            Console.WriteLine("Klasa1");
        }
    }

    class Klasa2 : Klasa1
    {
        public void metodaWirtualna()
        {
            Console.WriteLine("Klasa2");
        }
    }

   

    static void Wypisz(Interfejs obiekt)
    {
        obiekt.metodaWirtualna(); // wirtualna czy nie wirtualna, oto jest pytanie!
    }

    public static void Main()
    {
        Wypisz(new Klasa1());
        Wypisz(new Klasa2());        
    }
}

edytowany 3x, ostatnio: neves
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

Powiem tak:

  1. Nie odpowiedziałeś na moje pytanie.
  2. Pokazałeś 2 dowody na to, że C# jest takim samym WTFem jak C++. Zmiana typu referencji (nie obiektu na który wskazuje) skutkuje zmianą działania programu? W Javce mogę zamienić typ każdej referencji na typ wyższy w hierarchii (dopóki kod mi się kompiluje) i program zawsze będzie działał tak samo (no chyba, że ktoś robi instancja.metodaStatyczna() ale to samo w sobie jest WTFem).

Chodzi mi o semantykę wywołania funkcji z interfejsu, a nie o semantykę jej implementowania. To dwie różne sprawy. Wywoływanie funkcji z interfejsu to przechodzenie po v-table, natomiast implementowanie funkcji z interfejsu bądź nadpisywanie funkcji to konstruowanie v-table. Pokazałeś, że da się w dziwaczny sposób konstruować v-table, ale nie przeanalizowałeś przechodzenia po v-table.

Albo inaczej mówiąc, już przechodząc do sedna tego co chciałem przekazać: wywołując metodę na referencji typu interfejsu nie masz pewności czy wywołujesz metodę, która bezpośrednio implementuje metodę z interfejsu czy może metodę która nadpisuje inną metodę (która potencjalnie też nadpisuje inną metodę i tak dalej). Wywołując metodę z interfejsu nie masz pojęcia jak długo musisz przechodzić po v-table dopóki nie dostaniesz konkretnego obiektu, a więc działa to dokładnie tak samo jak z metodami wirtualnymi jawnie oznaczonymi jako wirtualne.

to oznacza że to nie są na poziomie języka metody virtualne, nie ma polimorfizmu

Polimorfizm nie wymaga metod wirtualnych. Haskell w ogóle nie ma metod czy funkcji wirtualnych, a pomorfizm to w nim podstawa.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 5x, ostatnio: Wibowit
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:2 dni
  • Lokalizacja:Kraków
  • Postów:1114
0

Bo Twoje pytanie jest podchwytliwe :), zacznijmy od tego że w C# póki co nie ma metod w interfejsach, są tylko kontrakty które typy implementujący dany interfejs muszą spełnić. Biorąc pod uwagę że nie mamy tych metod w interfejsach tylko w typach to pytanie jest trochę bez sensu. No ale dobrze, z punktu widzenia języka dla Klasy1 to wywołanie nie będzie virtualne, dla Klasy2 i Klasy3 to będzie wywołanie virtualne. Z poziomu ILA wszystkie będą wirtualne, bo tak akurat zostały interfejsy zaimplementowane w CLR.

To nie są żadne WTF, po prostu implementacja interfejsu nie powoduje że te metody z miejsca stają sie implicit virtualne, dlatego dostajemy dla Klasy2 dwa razy Klasa1, Klasa1, zamiast Klasa2, Klasa2 jakie byśmy dostali gdyby interfejsy dodawały implicit modyfikator virtual do implementujących je metod.


edytowany 1x, ostatnio: neves
ZK
  • Rejestracja:prawie 7 lat
  • Ostatnio:5 miesięcy
  • Postów:273
0

Tak wygląda metoda interfejsu : .method public hidebysig newslot virtual final - ma atrybut newsolt czyli ukrywa metodę która ma taką samą nazwę i parametry i jest zapieczętowana - nie można już jej przesłonić w klasie pochodnej

Tak wygląda metoda wirtualna w klasie bazowej : .method public hidebysig newslot virtual
a tak wygląda w klasie pochodnej : .method public hidebysig virtual - nie ma atrybutu newslot czyli przesłania a nie ukrywa .

Taką metodę możemy również zapieczętować : .method public hidebysig virtual final , żeby nie mogła być już więcej przesłaniana w kolejnych klasach dziedziczących po niej . (Kolejne klasy będą mogły jedynie odziedziczyć taką metodę albo ją ukryć )

Kopiuj
namespace ConsoleApp

{
    public class Program
    {
         public static void Main(string[] args)
        {
            KlasaA ob = new KlasaB(); // niejawna konwersja typu B na typ A ,ponieważ typ B dziedziczy po typie A
            
            ob.Metoda1("Ukrywanie metody");
            ob.Metoda2("Przesłanianie metody");
        }
    }


    class KlasaA
    {
        public virtual void Metoda1(string str)
        {
            Console.WriteLine($"{str} - KlasaA");
        }

        public virtual void Metoda2(string str)
        {
            Console.WriteLine($"{str} - KlasaA");
        }
    }

    class KlasaB : KlasaA
    {
        public new virtual void Metoda1(string str)  // ukrywanie - musi być ta sama nazwa i parametry
        {
                                                                            // np. public new string Metoda1(string str)
            Console.WriteLine("{str} - KlasaB");
        }

        public override void Metoda2(string str) // przesłanianie - musi być ten sam typ zwracany, ta sama nazwa i parametry
        {
            Console.WriteLine($"{str} - KlasaB");
        }
    }
}

A virtual method is introduced in the inheritance hierarchy by defining a virtual method . The definition can be marked newslot to always create a new virtual method for the defining class and any classes derived from it:  If the definition is marked newslot, the definition always creates a new virtual method, even if a base class provides a matching virtual method. A reference to the virtual method via the class containing the method definition, or via a class derived from that class, refers to the new definition (unless hidden by a newslot definition in a derived class). Any reference to the virtual method not via the class containing the method definition, nor via its derived classes, refers to the original definition.  If the definition is not marked newslot, the definition creates a new virtual method only if there is not virtual method of the same name and signature inherited from a base class. It follows that when a virtual method is marked newslot, its introduction will not affect any existing references to matching virtual methods in its base classes

edytowany 3x, ostatnio: Zimny Krawiec
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:2 dni
  • Lokalizacja:Kraków
  • Postów:1114
0
Wibowit napisał(a):

Wywoływanie funkcji z interfejsu to przechodzenie po v-table, natomiast implementowanie funkcji z interfejsu bądź nadpisywanie funkcji to konstruowanie v-table. Pokazałeś, że da się w dziwaczny sposób konstruować v-table, ale nie przeanalizowałeś przechodzenia po v-table.

Wywołując metodę z interfejsu nie masz pojęcia jak długo musisz przechodzić po v-table dopóki nie dostaniesz konkretnego obiektu, a więc działa to dokładnie tak samo jak z metodami wirtualnymi jawnie oznaczonymi jako wirtualne.

To nie działa tak samo w C# :), dla wszystkich par interfejs : typ implementujący jest definiowana w AppDomain globalna mapa IVMap. V-table jest używane dopiero wtedy kiedy dodasz modyfikator virtual czyli zrobisz z metody prawdziwą metodę wirtualną.


Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 18 godzin
0

@neves:
Wkleiłem twój kod w https://dotnetfiddle.net/ i zauważyłem, że mnie oszukałeś. Zamiast zaimplementować metody z interfejsu to przesłoniłeś metody!

Kopiuj
	class A
	{
		public virtual void m() {}
	}
	
	class B : A
	{
		public void m() {} // tu kompilator krzyczy, żeby dodać explicit "new", bo jak na razie sam sobie dodaje
	}

Abstrahując od tego, postanowiłem sprawdzić wydajność metod wirtualnych z interfejsów i z klas abstrakcyjnych. Oto kod (uwaga: długi i brzydki):

Kopiuj
using System;
using System.Collections.Generic;
using System.Diagnostics;
					
public class Program
{	
	interface InterfejsA
	{
		long metodaA(long x);
	}
	
	class KlasaA1 : InterfejsA
	{
		public long metodaA(long x)
		{
			return x * 5 + (x >> 3) + 1;
		}
	}
	
	class KlasaA2 : InterfejsA
	{
		public long metodaA(long x)
		{
			return x * 3 + (x >> 5) + 1;
		}
	}
	
	interface InterfejsB
	{
		long metodaB(long x);
	}
	
	class KlasaB1 : InterfejsB
	{
		public virtual long metodaB(long x)
		{
			return x * 5 + (x >> 3) + 2;
		}
	}
	
	class KlasaB2 : InterfejsB
	{
		public virtual long metodaB(long x)
		{
			return x * 3 + (x >> 5) + 2;
		}
	}
	
	class KlasaB1plus : KlasaB1
	{
		public override long metodaB(long x)
		{
			return x * 5 + (x >> 3) + 3;
		}
	}
	
	class KlasaB2plus : KlasaB2
	{
		public override long metodaB(long x)
		{
			return x * 3 + (x >> 5) + 3;
		}
	}
	
	abstract class KlasaC
	{
		public abstract long metodaC(long x);
	}
	
	class KlasaC1 : KlasaC
	{
		public override long metodaC(long x)
		{
			return x * 5 + (x >> 3) + 4;
		}
	}
	
	class KlasaC2 : KlasaC
	{
		public override long metodaC(long x)
		{
			return x * 3 + (x >> 5) + 4;
		}
	}
	
	class KlasaC1plus : KlasaC1
	{
		public override long metodaC(long x)
		{
			return x * 5 + (x >> 3) + 5;
		}
	}
	
	class KlasaC2plus : KlasaC2
	{
		public override long metodaC(long x)
		{
			return x * 3 + (x >> 5) + 5;
		}
	}
	
	public static void Main()
	{
		var listA = new List<InterfejsA>();
		var listB = new List<InterfejsB>();
		var listBplus = new List<InterfejsB>();
		var listC = new List<KlasaC>();
		var listCplus = new List<KlasaC>();
		for (int i = 0; i < 12345; i++)
		{
			listA.Add(new KlasaA1());
			listB.Add(new KlasaB1());
			listBplus.Add(new KlasaB1plus());
			listC.Add(new KlasaC1());
			listCplus.Add(new KlasaC1plus());
			listA.Add(new KlasaA2());
			listB.Add(new KlasaB2());
			listBplus.Add(new KlasaB2plus());
			listC.Add(new KlasaC2());
			listCplus.Add(new KlasaC2plus());
			listA.Add(new KlasaA1());
			listB.Add(new KlasaB1());
			listBplus.Add(new KlasaB1plus());
			listC.Add(new KlasaC1());
			listCplus.Add(new KlasaC1plus());
		}
		Stopwatch sw;
		long result = -1;
		for (int i = 0; i < 12; i++)
		{
			sw = Stopwatch.StartNew();
			for (int n = 0; n < 123; n++)
			for (int j = 0; j < 12345 * 3; j++)
			{
				result += listA[j].metodaA(result);
			}
			sw.Stop();
			Console.WriteLine("AAA      Time: " + sw.ElapsedMilliseconds + " ms");
			sw = Stopwatch.StartNew();
			for (int n = 0; n < 123; n++)
			for (int j = 0; j < 12345 * 3; j++)
			{
				result += listB[j].metodaB(result);
			}
			sw.Stop();
			Console.WriteLine("BBB      Time: " + sw.ElapsedMilliseconds + " ms");
			sw = Stopwatch.StartNew();
			for (int n = 0; n < 123; n++)
			for (int j = 0; j < 12345 * 3; j++)
			{
				result += listBplus[j].metodaB(result);
			}
			sw.Stop();
			Console.WriteLine("BBB plus Time: " + sw.ElapsedMilliseconds + " ms");
			sw = Stopwatch.StartNew();
			for (int n = 0; n < 123; n++)
			for (int j = 0; j < 12345 * 3; j++)
			{
				result += listC[j].metodaC(result);
			}
			sw.Stop();
			Console.WriteLine("CCC      Time: " + sw.ElapsedMilliseconds + " ms");
			sw = Stopwatch.StartNew();
			for (int n = 0; n < 123; n++)
			for (int j = 0; j < 12345 * 3; j++)
			{
				result += listCplus[j].metodaC(result);
			}
			sw.Stop();
			Console.WriteLine("CCC plus Time: " + sw.ElapsedMilliseconds + " ms");
		}
		Console.WriteLine(result);
	}
}

Wyniki na .NET 4.7.2 odpalone wprost w dotnetfiddle:

Kopiuj
AAA      Time: 98 ms
BBB      Time: 98 ms
BBB plus Time: 100 ms
CCC      Time: 85 ms
CCC plus Time: 84 ms
AAA      Time: 98 ms
BBB      Time: 94 ms
BBB plus Time: 91 ms
CCC      Time: 83 ms
CCC plus Time: 97 ms
AAA      Time: 114 ms
BBB      Time: 94 ms
BBB plus Time: 95 ms
CCC      Time: 80 ms
CCC plus Time: 80 ms
AAA      Time: 95 ms
BBB      Time: 94 ms
BBB plus Time: 94 ms
CCC      Time: 81 ms
CCC plus Time: 80 ms
AAA      Time: 93 ms
BBB      Time: 92 ms
BBB plus Time: 93 ms
CCC      Time: 84 ms
CCC plus Time: 80 ms
AAA      Time: 96 ms
BBB      Time: 101 ms
BBB plus Time: 97 ms
CCC      Time: 83 ms
CCC plus Time: 82 ms
AAA      Time: 92 ms
BBB      Time: 93 ms
BBB plus Time: 92 ms
CCC      Time: 82 ms
CCC plus Time: 80 ms
AAA      Time: 91 ms
BBB      Time: 92 ms
BBB plus Time: 95 ms
CCC      Time: 83 ms
CCC plus Time: 78 ms
AAA      Time: 92 ms
BBB      Time: 94 ms
BBB plus Time: 101 ms
CCC      Time: 85 ms
CCC plus Time: 82 ms
AAA      Time: 96 ms
BBB      Time: 95 ms
BBB plus Time: 91 ms
CCC      Time: 81 ms
CCC plus Time: 84 ms
AAA      Time: 92 ms
BBB      Time: 96 ms
BBB plus Time: 102 ms
CCC      Time: 83 ms
CCC plus Time: 84 ms
AAA      Time: 94 ms
BBB      Time: 96 ms
BBB plus Time: 93 ms
CCC      Time: 81 ms
CCC plus Time: 80 ms
-16423156767009094

Wychodzi na to, że:

  • dodanie jawnego słówka kluczowego virtual nie ma wpływu na wydajność
  • nadpisywanie zaimplementowanej metody nie ma wpływu na wydajność
  • wywołanie metody wirtualnej z abstrakcyjnej klasy jest ciut szybsze niż wywołanie metody wirtualnej z interfejsu

Dodatkowo:

  • wywalenie klas "plus" nie ma wpływu na wydajność pozostałych, a więc nie ma znaczenia ile razy metody są nadpisywane

Spróbuję jeszcze odpalić ten kod u siebie na .NET Core 2.1.503 na Ubuntu.
Aktualizacja:
OK, odpaliłem. Czasy rozkładają się tak samo, ale są jakieś 2.5x lepsze (pewnie mam 2.5x szybszego procka).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
ZK
  • Rejestracja:prawie 7 lat
  • Ostatnio:5 miesięcy
  • Postów:273
0

Spróbujcie zapieczętować te metody override dodając sealed i sprawdźcie czasy czy coś się zmieni . Sam jestem ciekawy

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)