Tipos
Clases, valores, propiedades, herencia, interfaces, enums, registros, tuplas y tipos anulables en Antril.
En esta página
Antril tiene estas categorías de tipos:
| Categoría | Qué es |
|---|---|
class |
Un tipo referencia: las variables comparten un mismo objeto. Una clase base y cualquier cantidad de interfaces. |
base class |
Una clase que no se puede crear directamente, solo derivar. |
value |
Un tipo valor: asignarlo o pasarlo lo copia. Implementa interfaces, no tiene clase base. |
record class, record value |
Campos fijados por el constructor, igualdad por contenido, copias con with. |
interface |
Métodos y propiedades que un tipo promete, con cuerpos predeterminados opcionales. |
enum, bitflag enum |
Un conjunto cerrado de valores con nombre; los flags se combinan con |. |
exception, attribute |
Clases derivadas de System.Exception y System.Attribute (consulta Errores). |
Clases
import System;
into namespace Bank
{
public class Account
{
public:
new(str owner, i32 start)
{
this.owner = owner;
balance = start;
Opened += 1;
}
str Owner { get => owner; }
i32 Balance { get { return balance; } private set { balance = value; } }
static i32 Opened { get; set; }
Deposit(i32 amount)
{
Balance = balance + amount;
}
private:
str owner;
i32 balance;
}
Main()
{
Account a = new Account("Ana", 10);
a.Deposit(5);
Account b = a;
b.Deposit(1);
Console.WriteLine(a.Owner + " has " + a.Balance);
Console.WriteLine(Account.Opened + " account opened");
}
}
larva run
Ana has 16
1 account opened
b = a copia la referencia: a y b son la misma cuenta.
Secciones de acceso
Los miembros se agrupan bajo etiquetas de acceso: public:, private:, internal:, inherited:, friendly: y file:. Una etiqueta se aplica hasta la siguiente o hasta el final del bloque, y se puede repetir. Un modificador escrito en una declaración individual tiene prioridad sobre la etiqueta.
Por convención, la etiqueta va una tabulación más adentro que su bloque, y sus miembros una tabulación más; de lo contrario, el compilador emite una advertencia.
| Acceso | Visible desde |
|---|---|
public |
Cualquier lugar, también otras bibliotecas. |
private |
El propio tipo y sus tipos anidados. Es el valor predeterminado para los miembros. |
internal |
La misma biblioteca o programa. Es el valor predeterminado para los tipos de nivel superior. |
inherited |
El tipo y los tipos derivados de él. |
friendly |
El tipo y sus amigos, declarados con [FriendOf(Other)] (la amistad es mutua). |
file |
El mismo archivo fuente. |
Los miembros de interfaces, los operadores y los valores de enums siempre son públicos. Un proyecto puede cambiar el acceso predeterminado de las declaraciones que no indican ninguno con access.default en su archivo .queen.
Campos y constructores
Los campos se declaran como variables. Los campos de tipos valor empiezan en cero; un campo readonly solo se asigna en el constructor.
Un constructor es new(…). Una clase que no tiene ninguno recibe uno vacío. Los constructores se pueden sobrecargar, y las formas de abajo ahorran escritura:
import System;
into namespace Geometry
{
public value Size
{
public:
i32 Width;
i32 Height;
// No body: each parameter is stored in the field of the same name.
new(i32 Width, i32 Height);
// Calls the other constructor.
new(i32 side) : (side, side);
i32 Area { get => Width * Height; }
}
public class Label
{
readonly str text;
public:
new(str text) { this.text = text; }
override ToString() : str => "[" + text + "]";
}
Main()
{
Size square = new Size(4);
Console.WriteLine(square.Area);
Console.WriteLine(new Label("ok"));
}
}
larva run
16
[ok]
Una clase puede tener un destructor, destroy(). Se ejecuta una vez, cuando el objeto deja de existir; cuándo ocurre eso depende de cómo el programa gestiona la memoria (consulta Memoria).
Propiedades
| Forma | Qué es |
|---|---|
i32 Count { get; set; } |
Un campo que se lee y se escribe como propiedad. |
i32 Count { get; private set; } |
Lo mismo, pero solo el propio tipo lo asigna. |
i32 Area { get => w * h; } |
Se calcula cada vez que se lee. |
i32 Balance { get { … } private set { … } } |
Cuerpos para cada descriptor de acceso; dentro de set, el nuevo valor es value. Un descriptor de acceso puede ser más restringido que la propiedad. |
Miembros estáticos y clases estáticas
Los campos, propiedades y métodos static pertenecen al tipo: Account.Opened. Una static class solo tiene miembros estáticos (sus miembros son estáticos sin necesidad de indicarlo) y no se puede usar como tipo de una variable:
public static class Settings
{
public:
static i32 Retries = 3;
Describe() : str => "retries: " + Retries;
}
Los tipos se pueden anidar dentro de otros tipos; desde fuera se nombran como Outer.Inner.
Valores
Un value se copia cuando se asigna o se pasa. Sus métodos trabajan sobre el original cuando se llaman sobre una variable.
import System;
public value Point
{
public:
i32 X;
i32 Y;
new(i32 X, i32 Y);
Length2() : i32 => X * X + Y * Y;
Move(i32 dx) { X = X + dx; }
}
Main()
{
Point p = new Point(3, 4);
Point q = p;
q.X = 0;
p.Move(1);
Console.WriteLine(p.Length2() + " " + q.Length2());
}
larva run
32 16
Herencia e interfaces
Una clase deriva de una sola clase base e implementa cualquier cantidad de interfaces: class Circle : Shape, IDrawable.
- Los métodos no son virtuales a menos que lo indiquen.
virtualpermite que las clases derivadas reemplacen un método, yoverridelo reemplaza con la misma firma. - En una
base class, un método sin cuerpo debe escribirlo cada clase derivada. : base(args)después de los parámetros de un constructor llama al constructor de la clase base.- De una
final classno se puede derivar;final overrideimpide que un método se siga reemplazando. - Una interfaz enumera métodos y propiedades. Puede dar un cuerpo predeterminado, que los tipos que la implementan heredan.
import System;
into namespace Shapes
{
public interface IShape
{
Area() : f64;
Describe() : str => "a shape of area " + Area();
}
public base class Shape : IShape
{
public:
Area() : f64;
virtual Name() : str => "shape";
}
public class Circle : Shape
{
f64 radius;
public:
new(f64 radius) { this.radius = radius; }
override Area() : f64 => 3.14 * radius * radius;
override Name() : str => "circle";
}
public final class Square : Shape
{
f64 side;
public:
new(f64 side) { this.side = side; }
override Area() : f64 => side * side;
}
Main()
{
List<Shape> shapes = new List<Shape>();
shapes.Add(new Circle(1));
shapes.Add(new Square(2));
loop (Shape s in shapes)
Console.WriteLine(s.Name() + ": " + s.Area());
IShape i = new Square(3);
Console.WriteLine(i.Describe());
Shape first = shapes[0];
if (first is Circle c)
Console.WriteLine("a circle of area " + c.Area());
Square? square = first as Square;
Console.WriteLine(square == null);
}
}
larva run
circle: 3.14
shape: 4
a shape of area 9
a circle of area 3.14
true
x is T comprueba un tipo, y x is T name además da el valor con ese tipo. x as T da un T?, que es null cuando x no es un T.
Un value también puede implementar interfaces. Si se guarda en una variable del tipo de la interfaz, se copia dentro de una caja (boxing).
Toda clase deriva de any (System.Any), que tiene ToString(), Equals y Hash(). Reemplaza ToString() para controlar cómo se imprime un objeto.
Enums
Un enum es un conjunto cerrado de valores con nombre sobre un tipo base: un entero (i32 por defecto), str o un value.
import System;
into namespace Palette
{
public enum Size { Small, Medium = 5, Large }
public enum Word : str { Hello = "hola", World }
public bitflag enum Access : u8 { Read, Write, Execute, All = declbits }
public record value Rgb
{
public:
u8 R;
u8 G;
u8 B;
new(u8 R, u8 G, u8 B);
}
public enum Color : Rgb
{
Red = new Rgb(255, 0, 0),
Green = new Rgb(0, 255, 0),
}
Main()
{
Size s = Size.Large;
Console.WriteLine(s + " is " + (i32)s);
Console.WriteLine((str)Word.Hello + " " + (str)Word.World);
Access a = Access.Read | Access.Write;
Console.WriteLine((a & Access.Write) == Access.Write);
Console.WriteLine(a.HasFlag(Access.Execute));
Console.WriteLine((u8)Access.All);
Rgb green = (Rgb)Color.Green;
Console.WriteLine(green);
}
}
larva run
Large is 6
hola World
true
false
7
Rgb { R = 0, G = 255, B = 0 }
- Los miembros enteros sin valor toman el del anterior más 1, empezando en 0. Un miembro
strsin valor es su propio nombre. - Un enum se convierte desde y hacia su tipo base solo con un cast:
(i32)s,(Size)6. ToString()(y la impresión) da el nombre del miembro.- En un
bitflag enum(con baseu32por defecto), los miembros sin valor toman el siguiente bit libre: 1, 2, 4…|,&,^y~conservan el tipo del enum.declbitsson todos los bits declarados,allbitstodos los bits de la base.HasFlag,HasAny,With,WithoutyTogglecomprueban y cambian bits. - Los miembros se pueden calcular a partir de otros miembros y constantes:
ReadWrite = Read | Write. - Con una base
value, cada miembro es un valor de ese tipo. Para convertir un valor de vuelta al enum ((Color)rgb) hace falta igualdad por contenido: unrecord valueo unop ==.
Registros
Un record class o record value son datos: el constructor asigna sus campos y estos no cambian después. Los registros se comparan por contenido, imprimen sus campos, calculan su hash para usarse como claves de diccionario y se copian con cambios usando with.
import System;
into namespace People
{
public record class Person
{
public:
str Name;
i32 Age;
new(str Name, i32 Age);
}
Main()
{
Person ana = new Person("Ana", 31);
Person older = ana with { Age = 32 };
Console.WriteLine(ana);
Console.WriteLine(ana == new Person("Ana", 31));
Console.WriteLine(older.Age);
if (ana is Person(var name, > 30))
Console.WriteLine(name + " is over 30");
Console.WriteLine(older is { Name: "Ana", Age: 32 });
}
}
larva run
Person { Name = Ana, Age = 31 }
true
32
Ana is over 30
true
Un registro de clase no deriva de otra clase y no se puede derivar de él; sí puede implementar interfaces.
Tuplas
(T1, T2, …) es un tipo tupla y (a, b, …) crea una. Los elementos son Item1, Item2… Las tuplas son registros: se comparan por contenido, se imprimen como (1, one) y sirven como claves de diccionario.
import System;
MinMax(i32[] values) : (i32, i32)
{
i32 low = values[0], high = values[0];
loop (i32 v in values)
{
if (v < low)
low = v;
if (v > high)
high = v;
}
return (low, high);
}
Main()
{
(i32, i32) range = MinMax([4, 9, 1, 7]);
Console.WriteLine(range);
Console.WriteLine(range.Item2 - range.Item1);
(i32, str) pair = (1, "one");
if (pair is (1, var word))
Console.WriteLine(word);
Dictionary<(i32, i32), str> grid = new Dictionary<(i32, i32), str>();
grid[(0, 1)] = "a";
Console.WriteLine(grid[(0, 1)]);
}
larva run
(1, 9)
8
one
a
(…) es un System.ValueTuple, cuyos elementos se pueden cambiar. ToTuple() da un System.Tuple, una clase cuyos elementos son fijos.
Una tupla que contiene objetos, como (i32, str), no puede ser el argumento de tipo de un tipo genérico (List<(i32, str)>). Las tuplas de números y valores simples sí pueden: Dictionary<(i32, i32), str>.
Tipos anulables
Un tipo sin ? nunca contiene null. T? sí puede:
import System;
public class Person
{
public:
str Name;
new(str Name);
}
Find(str name) : Person?
{
if (name == "ana")
return new Person("Ana");
return null;
}
Main()
{
Person? found = Find("ana");
Person? missing = Find("bob");
Console.WriteLine(found?.Name ?? "nobody");
Console.WriteLine(missing?.Name ?? "nobody");
i32? count = null;
Console.WriteLine(count ?? -1);
count = 7;
Console.WriteLine(count.HasValue);
Console.WriteLine(count.Value);
if (found is Person p)
Console.WriteLine("found " + p.Name);
}
larva run
Ana
nobody
-1
true
7
found Ana
?.lee un miembro, o da null cuando el lado izquierdo es null.??da su lado izquierdo salvo que sea null.x is T namecomprueba que haya un valor y le da un nombre.- Para tipos valor,
T?tieneHasValueyValue; leerValuecuando no hay ninguno detiene el programa.
Siguiente: Funciones y genéricos.
Editar esta página en GitHub Reportar un problema