# `static` methods, `static readonly`, `static class` and extension methods

> A `static` member belongs to the type rather than to any instance and is reached on the type name — `Money.Round(x)`, `Limits.Max`. A static method has no `this`. An IMMUTABLE static (`static readonly`, a getter-only static property) holds a fixed value; a static that can be assigned is refused. A `static class` holds only static members, and is where C# extension methods live — `this string s` on the first parameter makes `"hi".Shout()` work.

<!-- id: class-static · area: class · stability: stable · html: https://osysharp.com/reference/class/static/ -->

## Summary        {#summary}
A `static` method is called on the type, not on a value:

```osy title="a static method, called on the type name" test app=class-static
class Money {
  public decimal Amount;

  public static decimal Round(decimal value) {
    return Math.Round(value, 2);
  }
}

decimal RoundAPrice() {
  return Money.Round(19.999m);       // on the TYPE — there is no Money instance here
}
```

Reach for it when the operation is about the type but not about any particular value of it — a conversion, a
validation, a calculation over its arguments.

## Signature      {#signature}
```osy syntax
static <Return> <Name>(<params>) { … }
public static <Return> <Name>(<params>) { … }
public static readonly <Type> <Name> = <value>;
public static <Type> <Name> => <value>;
static class <Name> { <static members> }
public static <Return> <Name>(this <Type> value, <params>) { … }     // in a static class: an extension method
```

`static` goes after the visibility word, as in C#. On a method it means "no instance"; on a field or property it is
allowed only when the value cannot be assigned (`readonly`, or a getter with no setter).

## Description    {#description}

### A static method has no `this`   {#no-this}
That is the whole of the rule, and everything else follows from it. There is no instance, so there is nothing for an
instance field to be read from:

```osy title="✗ a static method reaching for instance state" syntax
class Money {
  public decimal Amount;

  public static decimal Doubled() {
    return Amount * 2m;              // ✗ 'Amount' belongs to an INSTANCE
  }

  public static decimal Half() {
    return this.Amount / 2m;         // ✗ `this` has no meaning in a static method
  }
}
```

Take the value as a parameter instead — which is usually what the method wanted anyway:

```osy title="pass in what the method needs" test app=class-static
class Tax {
  public static decimal Net(decimal gross, decimal rate) {
    return gross / (1m + rate);
  }
}
```

### What a static method CAN see of its own class   {#what-it-sees}
Its other static methods and its `const` values, both by bare name — the same rule as C#:

```osy title="statics reach statics, and consts, unqualified" run app=class-static
class Rate {
  public const decimal Standard = 0.25m;

  public static decimal Apply(decimal amount) {
    return amount * (1m + Standard);      // a const, by bare name
  }

  public static decimal ApplyTwice(decimal amount) {
    return Apply(Apply(amount));          // another static, by bare name
  }
}

[Test]
void Statics_Reach_Statics_And_Consts() {
  Assert.Equal(125m, Rate.Apply(100m));
  Assert.Equal(156.25m, Rate.ApplyTwice(100m));
}
```

An **instance** method may call its class's static methods the same way — it simply does not pass its `this` along:

```osy title="an instance method calling its type's static" test app=class-static
class Line {
  public decimal Gross;

  public decimal Net() {
    return Tax.Net(Gross, 0.25m);
  }
}
```

### Call it on the type, never through a value   {#call-form}
The two directions are both compile errors, and each says which spelling to use:

```osy syntax
var m = new Money { Amount = 1m };

m.Round(2.5m);          // ✗ 'Money.Round' is static — call it as `Money.Round(…)`
Money.Amount;           // ✗ 'Money.Amount' is an instance member — access it through an instance
```

The type may be written in full wherever its short name may: `Acme.Billing.Money.Round(2.5m)` and
`Acme.Billing.Money.Zero` are the same members, reached from a file that does not import `Acme.Billing`. A member the
class does not have is refused by the class's name: *the class 'Acme.Billing.Money' has no static member 'Nope'*.

Refusing the first is C#'s rule too, and it is worth the strictness: `m.Round(…)` reads as though the method can see
`m`, and it cannot — the receiver would be evaluated and discarded.

### Statics inherit   {#inheritance}
A static method declared on a base class is callable on a derived one, like any other inherited member:

```osy title="a subclass inherits its base's statics" test app=class-static
class Shape {
  public string Name;
  public static decimal Zero() { return 0m; }
}

class Circle : Shape {
  public decimal Radius;
}

decimal ZeroThroughTheSubclass() {
  return Circle.Zero();          // Shape declares it; Circle inherits it
}
```

It cannot be `virtual`, `override` or `abstract`, and combining them is a compile error. Those words choose a body
from the *receiver's* runtime type, and a static call has no receiver to choose by.

### Static methods overload   {#overloads}
Exactly like instance methods — a name maps to a set, and the call site picks by the arguments. See
[Method overloads](https://osysharp.com/reference/class/overloads/) for the rule.

```osy title="two statics sharing a name" test app=class-static
class Fmt {
  public static string Of(decimal d) { return d.ToString(); }
  public static string Of(decimal d, string unit) { return d.ToString() + " " + unit; }
}
```

### A static field must be immutable   {#no-static-fields}
A static that can be ASSIGNED is refused, in as many words. One host process serves many applications, so a mutable
static would be state shared by all of them, with no per-app copy to reset:

```osy syntax
class Counter {
  public static decimal Total;         // ✗ cannot be a MUTABLE `static`
}
```

An IMMUTABLE one is fine, because there is nothing in it anyone can change — see [`static readonly`](#static-readonly)
below. For the rest:

| what you want | reach for |
|---|---|
| a fixed value the whole program shares | `static readonly` (any value) or `const` (a literal) |
| a value that belongs to one object | an ordinary field |
| state that outlives a request | an `entity` — stored, per-app, and secured |

For the same reason there is no static **constructor**: each `static readonly` is given its value where it is declared.

### A fixed value on the type: `static readonly`   {#static-readonly}
`static readonly` and a getter-only static property are read on the type name, or by bare name inside the class —
C#'s rule exactly:

```osy title="immutable statics, read on the type and by bare name" run app=class-static
class Shipping {
  public static readonly decimal FreeFrom = 500m;
  public static int MaxParcels => 3;
  public static readonly string[] Carriers = ["PostNord", "DHL"];

  public static bool IsFree(decimal total) => total >= FreeFrom;   // bare name, inside the class
}

[Test]
void Immutable_Statics_Read_Like_Fields() {
  Assert.Equal(500m, Shipping.FreeFrom);
  Assert.Equal(3, Shipping.MaxParcels);
  Assert.Equal(2, Shipping.Carriers.Length);
  Assert.True(Shipping.IsFree(600m));
}
```

### When does the initializer run, and how often?   {#initialized-once}
ONCE — the first time the field is read — and the value is then kept, as C#'s static initializer keeps it: once per
running version of the app on each server, and once per page load in the browser. So `Guid.NewGuid()` is one id, and a
list the field holds keeps what is added to it. A getter-only static PROPERTY (`static int Max => …;`) is the other C#
shape and runs on every read:

```osy title="a static readonly is initialized once; a static property on every read" run app=class-static
static class Boot {
  public static readonly Guid Started = Guid.NewGuid();
  public static readonly List<string> Seen = new List<string>();
  public static Guid Fresh => Guid.NewGuid();
}

[Test]
void A_Static_Readonly_Is_Kept() {
  Assert.Equal(Boot.Started, Boot.Started);
  Boot.Seen.Add("a");
  Assert.Equal(1, Boot.Seen.Count);
  Assert.True(Boot.Fresh != Boot.Fresh);
}
```

⚠ Because it is shared by every request the server handles, the initializer may not read stored data, who is signed
in, or do work with an effect — the first caller's answer would be every later caller's. That is a compile error that
says so; read such a value in a function, or as a static property, which runs per read. And a `static readonly` cannot
be assigned after it is declared (C#'s rule); what it holds can still be changed through it.

### A holder of helpers: `static class`   {#static-class}
A `static class` holds only static members — C# refuses an instance member in one (CS0708), and so does Osy#, naming
the member. It is never created (`new Text()` is refused) and nothing derives from it:

```osy title="a static class of helpers" test app=class-static
static class Slug {
  public static readonly string Separator = "-";
  public static string Of(string title) => title.Trim().ToLower().Replace(" ", Separator);
}

string SlugFor(string title) => Slug.Of(title);
```

It takes a visibility like any class, written before `static` as in C#:

```osy title="public static class" test app=class-static-public
public static class Measures {
  public const int Dozen = 12;
  public static int Dozens(int n) => n * Dozen;
}

int Eggs() => Measures.Dozens(3);
```

### Adding a verb to a type you do not own: extension methods   {#extension-methods}
`this` on the FIRST parameter of a static method in a `static class` makes an extension method: the value it is
called on is passed as that parameter. `"Hello".Shout()` is `Text.Shout("Hello")`:

```osy title="extension methods on string, on int and on your own class" run app=class-static
static class Text {
  public static string Shout(this string s) => s.ToUpper() + "!";
  public static string Wrap(this string s, string with) => with + s + with;
}

static class Numbers {
  public static bool IsEven(this int n) => n % 2 == 0;
}

class Order { public int Id; }

static class OrderLinks {
  public static string Href(this Order o) => $"/orders/{o.Id}";
}

[Test]
void Extension_Methods_Read_Like_Members() {
  Assert.Equal("HELLO!", "Hello".Shout());
  Assert.Equal("*a*", "a".Wrap("*"));
  Assert.True(4.IsEven());
  var orders = new List<Order> { new Order { Id = 1 }, new Order { Id = 2 } };
  Assert.Equal("/orders/1,/orders/2", string.Join(",", orders.Select(o => o.Href())));
}
```

The rules are C#'s:

- **An instance member wins.** An extension is tried only when nothing on the type itself applies — an extension
  named `ToUpper` never replaces `string.ToUpper()`.
- **Two that both apply are an error**, naming both — call the one you mean on its class (`Text.Shout(s)`).
- **It is in scope wherever its class is.** An extension in a kit is available to every app that uses the kit.
- `this` anywhere else — on a method outside a static class, or on a parameter that is not the first — is refused,
  saying where it belongs.

## See also       {#see-also}
- [class methods](https://osysharp.com/reference/class/methods/) — instance methods, and the member-body surface a static method shares
- [Passing a function by name — method groups](https://osysharp.com/reference/function/method-groups/) — passing `Slug.Of` where a `Func<string, string>` is wanted
- [Method overloads](https://osysharp.com/reference/class/overloads/) — how a call site picks from a set of same-named methods
- [Classes](https://osysharp.com/reference/class/index/) — fields, `const`, and what a class is
- [type visibility (public / internal)](https://osysharp.com/reference/types/visibility/) — `public`, `private` and `protected`, which combine with `static`
