Sobre C#

Apesar do nome, C# tem muito mais a ver com Java do que com C e C++, incluindo padrões de projeto, nomenclatura, forma de trabalho, organização, etc... Basicamente o que C#, C e C++ possuem em comum é ponteiros (e ainda assim não funciona da mesma forma, mas dificilmente você os utiliza em C#, por questões de segurança - mas vale lembrar que todo objeto é uma referência), e trabalha com tipos primitivos de uma forma mais próxima de C e C++ (inclusive na definição do tamanho deles: em C# há uint8, uint16, uint32, uint64, int8, int16, int32, int64, ...). Específico em relação a C++, C# também possui namespaces (o que é ótimo!).

Mas apesar da semelhança com Java, C# é uma linguagem bem elaborada, com bibliotecas com APIs bem fáceis de usar, não costuma ser difícil de lidar com ela, e ainda consegue manter uma certa liberdade ao programador e suas implementações costumam ter uma performance muito boa.

Para quem conhece Java, C# possui um sistema de get/set (adicionando Operator Overloading, o que é ótimo também), callbacks e lambdas muito bem feitos e fáceis de usar. Outra característica importante é que C# já possui inferência de tipo (via var) desde Visual C# 3.0 (lançado em 2007).

Há ainda, porém, a mesma restrição de Java quanto a arquivos: todo arquivo .cs deve expor uma classe pública no escopo mais externo (fora namespaces).

C# para programadores C

Esta seção é para programadores C que pretendem aprender C#, e portanto precisam estar avisados de alguns conceitos que não estão presentes ou são diferentes em C.

Caso você não seja um programador C, pode pular esta seção.

Básicos

Se você veio de C e sabe somente C, prepare-se para o mundo novo da Orientação a Objetos. A maior diferença notável entre instâncias de structs de C e objetos de C# é o fato de que objetos possuem métodos (i.e. funções do próprio objeto). No caso específico de Java, C# e Cobra, toda função é uma função-membro, ou seja, não existe função "solta", todas elas precisam estar dentro de uma classe. Isso inclui também a função main(), visto que, em C, um Hello World seria:

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

Enquanto que em C# seria:

using System;

public class INeedAClassForEverything {
    public static void Main() {
        Console.WriteLine("Hello, world!");
    }
}

Um pouco mais comprido, sim, devido principalmente à decisão de projeto da linguagem de que tudo precisa estar em uma classe. Bem, acostume-se.

Há várias construções que são quase iguais em ambas as linguagens, por exemplo, o que em C seria:

int sum = 0;
for (int i = 0; i < 10; ++i) {
    sum += 1;
}

printf("Sum: %d\n", sum);

if (sum > 20) {
    printf("Sum is bigger than 20\n");
} else if (sum > 10) {
    printf("Sum is between 10 and 20\n");
} else {
    printf("Sum is less/equal 10\n");
}

Em C# ficaria:

int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += i;
}

Console.WriteLine(string.Format("Sum: {0}", sum));

if (sum > 20) {
    Console.WriteLine("Sum is bigger than 20");
} else if (sum > 10) {
    Console.WriteLine("Sum is between 10 and 20");
} else {
    Console.WriteLine("Sum is less/equal 10");
}

Note o {0} em String.Format. 0 é o índice do parâmetro a ser substituído ali. Exemplo:

String.Format("{0} {1} {2} {3}", 1, true, 3.14f, Console.In);

Irá retornar uma String com:

1 true 3.14 SystemIO.Reader+NullStreamReader

Note também que foi possível colocar um Console.In na formatação de String, o que resultou em um texto estranho (SystemIO.Reader+NullStreamReader). Isso se dá porque o string.Format na verdade só chama o método ToString() de cada parâmetro, o que em alguns casos o padrão é mostrar o tipo do objeto. Métodos serão vistos melhor mais a frente.

Uma pequena tabela mostrando um pouco do que há em C de diferente/igual a C#:

RecursoCC#
PonteirosSimNão¹
Classes/ObjetosNãoSim
StructsSimStructs são ValueTypes², Classes são ReferenceTypes³
MacrosSimSim⁴
NamespacesNãoSim
Function-pointerSimPossui Delegates e Events

¹: Na verdade sim, mas apenas em um bloco unsafe.

²: ValueTypes

³: ReferenceTypes

⁴: C# tem macros, mas não da mesma forma que C: elas apenas definem símbolos que podem ser utilizados em #ifndef, por exemplo, mas não servem para definir constantes. Também não há a necessidade de include-guards.

Orientação a Objetos: Novidades em relação a C

Se você vem de C, é necessário entender um pouco sobre Orientação a Objetos (OO). Em C não existe Objeto no sentido de OO, então as estruturas são agregados de informações conforme você definir (para não sair infestando o programa com variáveis em todo lugar, definindo tipos que possuem propriedades próprias, como User possuir login e email). Ou seja, são muito mais organizacionais do que exatamente feitos para interagir entre si (comparando com objetos).

Em linguagens que possuem orientação a objetos, você tem, advinhe, objetos. Objetos são diferentes (inclusive conceitualmente) de estruturas do C.

Por simplicidade, um objeto possui atributos (as variáveis internas dele), constantes internas, e métodos (funções-membro).

Uma das ideias da orientação a objetos é a troca de mensagens entre os objetos, que se dá por meio de métodos (lembre-se de que operadores, ou seja, +, -, *, /, <<, >>, <, >, <=, >=, etc., também são mensagens - menos em Java e Object-Pascal, são umas das únicas linguagens orientadas a objeto em que operadores são procedurais - ou seja, fazem apenas comparações numéricas -; definir como esses operadores funcionam se chama operator overloading).

Em C, quando você instancia uma estrutura, os dados internos dela (suas variáveis) são carregados com lixo de memória, ou seja: foi cedido um espaço de memória para eles, mas esse espaço não foi zerado, então se a última ação em um dos bytes que foram cedidos foi escrever um "5" nele, esse 5 estará lá no meio da sua instância (a menos que você use um calloc). Não há uma ideia de "o que acontece quando sou inicializado?", que é chamado de construtor. Em OO você possui construtores, para os quais você pode passar parâmetros que dirão como o objeto será inicializado. Ou seja, em C, para você inicializar uma estrutura:

typedef struct {
    int first_value;
    int second_value;
    char third_value;
} my_struct;

int main(void) {
    my_struct instance = {
        .first_value = 5,
        .second_value = -1,
        .third_value = 'a',
    };

    // ...
}

Em Orientação a Objetos, quando esses valores precisam já estar designados antes do objeto estar pronto para uso (por exemplo, já ter uma ID, e que o programador não pode esquecer de dar o valor a ela), você possui construtores.

OBS: Construtores não servem apenas para definir o valor dos atributos. Um construtor de uma janela por exemplo pode também colocar cor de fundo padrão, conectar botões às funções que serão executadas ao clicar neles, etc.

No caso de C#, my_struct poderia ser implementado da seguinte forma:

class MyStruct {
    int firstValue;
    int secondValue;
    char thirdValue;

    // Construtores são definidos como funções sem retorno e com nome igual ao
    // da classe
    public MyStruct(int firstValue, int secondValue) {
        this.firstValue = firstValue;
        this.secondValue = secondValue;
        this.thirdValue = 'a'; // Supondo que o padrão de thirdValue seja 'a'
    }
}

public class Example {
    public static void Main() {
        // `instance` é inicializada como uma MyStruct com `firstValue` = 5,
        // `secondValue` = -1 e `thirdValue` = 'a'.
        var instance = new MyStruct(5, -1);
    }
}

Perceba que, em C#, objetos são criados utilizando o operador new. Para ReferenceTypes, isso significa fazer uma alocação dinâmica (ou seja, é análogo a chamar uma função que dá calloc e inicializa os valores da struct conforme conveniente). Em outras palavras, o construtor de MyStruct seria equivalente a, em C, ter uma função de inicialização para struct my_struct:

my_struct* new_my_struct(int first_value, int second_value) {
    my_struct* obj = calloc(1, sizeof(my_struct));

    *obj = (my_struct){
        .first_value = first_value,
        .second_value = second_value,
        .third_value = 'a',
    }

    return obj;
}

C# ainda tem um "jeito C# de fazer as coisas", que nada mais é do que, em vez de utilizar um construtor para setar os valores iniciais da classe, utilizar o que se chama de Initializer List (também presente em C++).

Funciona semelhante à inicialização de structs de C:

class MyStruct {
    int firstValue;
    int secondValue;
    char thirdValue = 'a'; // É possível já dar o valor padrão de um atributo
                           // na sua inicialização
}

public class Example {
    public static void Main() {
        MyStruct instance = new MyStruct {
            firstValue = 5,   // Como em C, é "," e não ";"
            secondValue = -1,
        };
    }
}

No caso, suponha que você só dê o valor de firstValue, mas não do secondValue. Qual será o valor de secondValue? Em C ele seria inicializado com lixo da memória, já em C# ele é inicializado com o valor padrão 0. Esse "valor padrão" varia conforme o tipo de dado, conforme na tabela:

Tipo de dadoValor padrão
Inteiros (int, short, ...)0
Reais (float, double, ...)0.0
boolfalse
char'\0'
Objetosnull¹

¹: null funciona igual ao NULL e quer dizer "não há um valor sendo guardado aqui". Significa que a variável não referencia algum trecho de memória alocada. Portanto, para não ter problemas tentando acessar atributos/métodos de objetos em cima de null, sempre inicialize seus objetos! i.e. dê variable = new Algo(...)). Se você tem uma variável com valor null e que todos possam acessar, alguma coisa está errada no seu programa, pois significa que alguém está em um estado inválido.

Classe e Objeto

Classe != Objeto. Classe é apenas a definição dela, é o "template" que define características de um tipo. Objeto é a instância de uma classe. Ou seja:

// Isso é uma classe
class MyClass {}

// Isso é um objeto do tipo MyClass
new MyClass();

Métodos

Métodos (a.k.a. "funções-membro") são funções de um objeto. Em OO, objetos podem definir, além de atributos, funções que operam sobre eles. Por exemplo, um objeto do tipo User pode precisar definir qual é o processo de renomear tal usuário, por exemplo:

public class User {
    string name;

    public void Rename(string name) {
        // Primeiro, checa-se se o nome é um nome válido
        if (string.IsNullOrEmpty(name)) {
            throw new InvalidArgument("Username cannot be empty or null.");
        }

        this.name = name;
    }
}

static void Foo() {
    var user = new User {
        name = "Josh",
    };

    user.Rename("Carl");

    Console.WriteLine(user.name); // => Carl

    user.Rename(""); // Erro
}

Static

Em C, como não há métodos, ter uma função solta é extremamente comum. Porém, em C#, tudo precisa estar dentro de uma classe: então como fazer com que uma função não dependa seja de um objeto? Para isso serve static:

public class Math {
    public static double square(double x) {
        return x * x;
    }
}

Herança

Em OO, você irá constantemente ouvir a respeito de herança, isso simplesmente é como se denomina quando uma classe A herda todos os métodos e atributos de outra, ou seja: se uma classe B herda A, isso significa que tudo que A tem, B também tem, mas o contrário não necessariamente é verdade.

Herança em C# pode ser feita com : <classe herdada> entre o nome da classe e o {:

public class A {
  int x;
}

public class B : A {
  int y;
}

public class Example {
  public static void Main() {
    var a = new A();
    var b = new B();
    bx = 5;   // => OK
    b.y = 10; // => OK
    ax = 5;   // => OK
    a.y = 10; // => Erro: A não possui o atributo `y`
  }
}

Inclusive, quando se diz que B herda A, também se está dizendo que todo B é também um A, e portanto toda função que aceita um A aceita também um B:

static void Foo(A a) {
    // ...
}

static void Bar(B b) {
    // ...
}

static void Test() {
    var a = new A();
    var b = new B();

    Foo(a); // OK
    Foo(b); // OK

    Bar(a); // Erro: `A` não pode ser enviado a algo que pede `B`
    Bar(b); // OK
}

Modificadores de acesso

Para finalizar a parte de orientação a objetos, apenas mostrar para que servem public, private, protected, que são os modificadores de acesso, utilizados na declaração de métodos, atributos e propriedades de objetos:

ModificadorAção
publicVisível para qualquer outra classe externa.
privateVisível apenas para a própria classe. Classes herdeiras não
poderão ver o que estiver em private da sua superclasse.
protectedVisível para a própria classe e classes-filhas.

Não tenho ponteiros. E agora?

C# até possui ponteiros, mas eles não vão poder ser usados de qualquer maneira como em C. Mas no fundo, em qualquer linguagem que não seja C, se você está mexendo com ponteiros, de duas uma:

  • Você está precisando manipular memória de uma maneira muito específica (como implementar um smart_pointer do C++, ou uma Lista_Encadeada, por exemplo, ou ainda utilizar uma arquitetura muito específica na qual endereços específicos de memória acessam registradores/IO importantes) e deve estar bastante atento aos cuidados com isso;
  • Você está fazendo algo de errado e essa com certeza não é a melhor forma de fazer o que está querendo.

No caso de C não há algum recurso da linguagem para lidar com referências sem explicitamente utilizar ponteiros, então não há outra alternativa. Tenha em mente que ponteiros são potencialmente inseguros e podem levar muito facilmente a erros, incluindo vazamento de memória ou dupla deleção se você não cuidar da forma como manuseia eles (e não é meramente uma questão de "basta ser muito bom na linguagem": ponteiros explícitos são inseguros para todo mundo, um profissional vai apenas ter menos problemas explícitos com eles).

"Referenciar alguma coisa" significa que, em vez de ter uma cópia de outro valor, você o acessa indiretamente¹. Uma referência pode ser implementada com um ponteiro (há diferença entre "ser um" e "ser implementado com um"), mas tudo depende de como o compilador vai tratá-las.

Para pegar a ideia de referências, segue um exemplo em C (repare nos comentários):

int main(void) {
    int a = 10;
    int b = a; // `b` é uma cópia de `a`
    int *c = &a; // `c` guarda o endereço de `a`

    // Neste momento:
    //        a = 10; b = 10; *c = 10

    a = 5;  // a =  5; b = 10; *c = 5
    b = 8;  // a =  5; b =  8; *c = 5
    *c = 9; // a =  9; b =  8; *c = 9

    // ...
}

No exemplo acima, pode-se dizer que c "referencia" a (na forma de ponteiros), então alterações ao valor em c alteram também a.

No caso de C#, toda variável que guarda um ReferenceType é uma referência, enquanto que uma que guarde um ValueType é "sempre" uma cópia (com ressalva de quando há a keyword ref antes de sua declaração:

// Lembre-se de que, em C#, toda `struct` define um ValueType
public struct Point
{
    int x;
    int y;
}


// Ainda em C#, toda `class` define um ReferenceType
public class Vertex
{
    int x;
    int y;
}


public static void Change(Point p) {
    p.x++;
}

public static void Change(Vertex p) {
    p.x++;
}

public static void Change(ref Point p) {
    p.x++;
}

public static void Main() {
    var p = new Point { x = 0, y = 0, };
    var v = new Vertex { x = 0, y = 0, };

    // Nesse caso, como `Point` é um `ValueType`, é enviada uma *cópia* de p
    // para a função `Change`.
    Change(p);

    // Neste momento:
    //     p: { x = 0, y = 0, }
    //     v: { x = 0, y = 0, }

    // Já como `Vertex` é um `ReferenceType`, é enviada uma *referência* a v
    // para a função `Change`. Isso significa que alterações feitas em `p`
    // também alteram `v`.
    Change(v);

    // Neste momento:
    //     p: { x = 0, y = 0, }
    //     v: { x = 1, y = 0, }

    // Se um `ValueType` precisar ser passado por referência (e a função em
    // questão aceitar), é possível enviá-lo com a keyword `ref` antes da
    // variável:
    Change(ref p);

    // Neste momento:
    //     p: { x = 1, y = 0, }
    //     v: { x = 1, y = 0, }
}

Também vale ressaltar que, como ValueTypes não são referências, eles não podem ser null (ou seja, não podem "referenciar ninguém") a menos que se diga que a variável em questão é "Nullable", o que é feito com um ? após o tipo:

public static void Main() {
    int i = null;       // Erro: Valuetypes não são "Nullable"
    int? j = null;      // OK: `j` é Nullable
    var k = (int?)null; // O mesmo que `j`, porém com inferência de tipo
}

Quando for necessária uma cópia de um ReferenceType, é possível aproveitar o método Clone(), disponível para objetos de algumas classes (especificamente as que herdem de Clonable):

public static void Main() {
    var v1 = new Vertex { x = 0, y = 0 } ;
    var v2 = x.Clone();

    v2.x = -1;

    Console.WriteLine(v1.x) // => 0
    Console.WriteLine(v2.x) // => -1
}

No caso acima, como v2 é uma cópia de v1, alterações em v2 não alteram v1 e vice-versa.

Aproveitando, deve-se tomar cuidado quanto a ValueTypes, pois toda vez que eles são atribuídos a outra variável ou enviados como argumentos para funções, sempre será uma cópia (como dito anteriormente, a menos que dito explicitamente via ref). Ou seja, tomar cuidado com situações como:

public struct Point {
    int x;
    int y;
}

public class Rect {
    Point p1;
    Point p2;
}

public static void Main() {
    var r = new Rect {
        p1 = new Point { x = 0, y = 0, },
        p2 = new Point { x = 5, y = 5, },
    };

    var p1 = r.p1; // Cuidado! `r.p1` é um ValueType, e portanto será feita uma
                   // cópia de `r.p1`.

    p1.x = 10;

    Console.WriteLine(r.p1.x); // => 0
    Console.WriteLine(p1.x);   // => 10
}

C# para programadores C++

Esta seção é para programadores C++ que pretendem aprender C#, e portanto precisariam conhecer os recursos de C# análogos aos de C++, ou entender quais os padrões em termos de linguagem, por exemplo: qual o padrão para quando ocorre cópia, move, etc.

Caso você não seja um programador C++, pode pular esta seção.

Principais Diferenças

(OBS: Este tutorial encontra-se bastante incompleto. Se puder, ficaremos gratos em receber um Pull Request seu)

Se você veio de C++, o caminho é bem mais simples quanto se você viesse de C.

Assim como foi feito com C, segue uma pequena tabela com algumas das diferenças entre C# e C++:

RecursoCC#
PonteirosSimNão¹
Classes/ObjetosSim. Não herdam por padrãoSim, e definem ReferenceTypes². Herdam de Object.
StructsSimSim, mas definem ValueTypes²
MacrosSimSim³
NamespacesSimSim
Function-pointerSimPossui Delegates e Events
HerançaSimSim
InterfaceSim (classes puramente virtuais)Sim (com a keyword interface)
TemplatesSimPossui Generics⁴

¹: Sim, mas apenas em um bloco unsafe.

²: ValueTypes e ReferenceTypes são conceitos já conhecidos em C++, mas vale ver em que contextos C# trata um ou outro.

³: C# tem macros, mas não da mesma forma que C: elas apenas definem símbolos que podem ser utilizados em #ifndef, por exemplo, mas não servem para definir constantes. Também não há a necessidade de include-guards.

Uma das maiores diferenças que você provavelmente irá notar é que, em C#, tudo precisa estar em uma classe, não existe nada “solto” (incluindo o main, que fica marcado como um método estático e os argumentos da linha de comando ficam em um array de strings).

No geral não haverão muitas diferenças na forma de modelar o código. Algumas nomenclaturas, apesar de iguais, funcionam de forma diferente. São elas:

using

  • Um using não pode ser utilizado dentro de qualquer escopo como em C++;
  • using, em C#, possui três funções:
    • Incluir namespaces, como o using do C++ mesmo, porém apenas no escopo geral;
    • Criar aliases para Namespaces;
    • Criar aliases para classes (serve tanto para resolver ambiguidade quanto para importar apenas o que precisar de uma determinada namespace).

struct

  • Não há a diferença de modificador de acesso padrão entre Classes (que em C++ é private) e Structs (que em C++ é public): o padrão de ambos é internal;
  • Structs, por serem ValueTypes, são sempre recebidas como cópia: não há como enviar uma referência de uma struct senão indicando explicitamente com a keyword ref (é necessário que a função também defina o parâmetro como ref, e nesse caso apenas refs podem ser passadas para aquele parâmetro em específico).

Outras diferenças menores

  • Para acessar um elemento de uma namespace se utiliza namespace.something em vez de namespace::something;
  • ++i e i++ não possuem diferença de performance;
  • TODO

std::optional

Em C#, apesar de que ValueTypes não são referências, é possível criar uma versão "Nullable" deles utilizanod um ? após o tipo. Ela acaba funcionando como std::optional, e pode ser vista aqui:

Algumas Features de C#

Para se alinhar com o idioma de C#, é interessante conhecer suas features. Esta seção apresenta algumas delas, apresentando exemplos de uso e explicações do funcionamento de cada uma, bem como quando utilizá-las.

Nullables

Nullables servem para guardar ValueTypes que podem receber o valor null, funcionando de forma bem semelhante a std::optional de C++ e Option de Rust. Segue um código de exemplo:

bool foo = true;          // `foo` é um bool normal
bool? bar = true;         // `bar` é um bool nullable
var foobar = (bool?)null; // Com inferência de tipo

if (foo && bar) {}    // true && false = false: não entra no if
if (foo && foobar) {} // true && ? = ?: entra ou não no if?
if (bar) {}           // true: entra no if
if (foobar) {}        // ?

if (bar.GetValueOrDefault(false)) {}    // bar = true, logo entra no if
if (bar.GetValueOrDefault(true)) {}     // bar = true, logo entra no if
if (foobar.GetValueOrDefault(false)) {} // foobar = null, logo pega o default,
                                        // que é falso, e portanto não entra no
                                        // if
if (foobar.GetValueOrDefault(true)) {}  // foobar = null, logo pega o default,
                                        // que é true, e portanto entra no if

Properties

Em linguagens OO, costumam ser padrões as seguintes regras:

  • Todo atributo deve ser inacessível às outras classes;
  • Se um atributo pode ser lido externamente, essa leitura deve se dar a partir de um getter;
  • Se um atributo pode ser escrito por elementos externos, essa escrita deve se dar a partir de um setter.

Em algumas delas, a forma como isso é feito é extremamente manual, como Java por exemplo:

public class A {
    private int something;
    private SomeType another;


    public int getSomething() {
        return something;
    }

    public void setSomething(int value) {
        something = value;
    }

    public SomeType getAnother() {
        return another;
    }

    public void setAnother(SomeType value) {
        another = value;
    }
}

Em C#, felizmente há uma maneira simples de lidar com getters e setters, que é através de Properties. Para quem conhece Ruby, é semelhante a attr_accessor, attr_reader e attr_writer: eles geram métodos que fazem a devida função de getter e setter para você. Para quem conhece Python, é como utilizar @property:

public class A {
    public int Something {
        get; set;
    }

    public SomeType Another {
        get; set;
    }
}

Em alguns casos, é necessário definir como esses métodos são implementados. Por exemplo, há momentos em que não é possível expor algum atributo como Property diretamente, então é necessário separá-lo como um atributo e utilizar a Property para o expor:

public class A {
    private Type _somethingThatCantBeAProperty;
    public Type SomethingThatCantBeAProperty {
        // get e set são efetivamente métodos, então...podemos simplesmente
        // utilizar os métodos para retornar os valores que queremos.
        get
        {
            return _somethingThatCantBeAProperty;
        }
        set
        {
            // Em um setter, o valor passado após o "=" é dado como `value`.
            _somethingThatCantBeAProperty = value;
        }
    }
}

public class Application {
    public static void Main(string[] args) {
        var a = new A();

        // chama o set
        a.SomethingThatCantBeAProperty = new Type();

        // chama o get
        Console.WriteLine(a.SomethingThatCantBeAProperty);
    }
}

Quando utilizar properties: sempre que você tiver um atributo público (com getter e setter), faça-o como uma property (ou encapsule-o com, caso não seja possível ele em si ser uma property). Não utilize properties caso o setter ou o getter envolva algum cálculo! Nesse caso, use um método explícito, a fim de manter a intuitividade de que o acesso aquele valor não é necessariamente trivial/barato.