Interface
Forms that compile (23)
Section titled “Forms that compile (23)”| Syntax | Description | Note |
|---|---|---|
interface IShape { float Area(); } | Declaring and implementing an interface | Writing a body errors (Forms that don’t compile (9)). |
interface IValue { int Id { get; } } | Interface properties | |
public int Id { get { return 1; } } | Implementing a computed property | It can’t be written as an explicit implementation (Forms that don’t compile (9)). |
interface IFast : IGadget | Interface inheritance | |
struct Box : IShape | Implementation by a value type | |
record Tag(int N) : ILabel | Implementation by a record | |
class C : TsukimiBehaviour, IGadget | Implementation by a Behaviour | this can be assigned to a variable of that interface type. |
struct Both : IGadget, IReset | Multiple implementations by a value type | |
class C : TsukimiBehaviour, IGadget, IReset | Multiple implementations by a Behaviour | |
IShape s = new Box(); | Holding in a local variable | |
void Use(IShape s) | Receiving as a parameter | |
IShape Make() | Returning as a return value | |
IShape[] all = new IShape[2]; | Storing a value type in an interface array | |
public IGadget other; | Holding in an interface-typed field | |
other is IReset | Type-testing against another interface | It can’t be written on an interface holding a value type (Forms that don’t compile (9)). |
public IGadget[] all; | Holding in an interface-typed array | |
GetComponent<IGadget>() | Getting a component by interface | |
other != null | Comparing with null | |
other?.Tick() | Null-conditional call | |
s is Box | Testing for a concrete type | Whether it implements another interface can’t be tested (Forms that don’t compile (9)). |
s switch { Box b => ..., _ => ... } | Branching on a concrete type | |
s switch { Box b => ..., Dot d => ... } | Branching without a default case | The same as union. |
where T : IShape | Constraining a type argument by interface |
Forms that don’t compile (9)
Section titled “Forms that don’t compile (9)”| Syntax | Description | Error | Reason | Alternative |
|---|---|---|---|---|
int IValue.Id { get { return 1; } } (in the implementing type) | Explicitly implementing an interface property | TUKI0001 | by design | Implement it as a public property |
v.Id (v is an interface attached to a Behaviour) | Reading a property through an interface attached to a Behaviour | TUKI0001 | by design | Make the interface side a method (int Id();) instead (a value type implementation can still read it as a property) |
int Twice() { return Value() * 2; } | Writing a default implementation on the interface | TUKI0001 | by design | Write it in each implementing type |
float IShape.Area() { ... } | Implementing it explicitly | TUKI0001 | by design | Implement it as an ordinary public method |
private IShape held; | Holding an interface attached to a value type in a field | TUKI0001 | not yet | Pass it through a local variable or parameter. An interface attached to a Behaviour can be held in a field. |
Box b = (Box)s; | Casting from an interface to a concrete type | TUKI0001 | not yet | Receive it with a type pattern (s switch { Box b => ... }) |
IGadget g = new Widget(); g is IReset | A type test against an interface holding a value type | TUKI0001 | not yet | Receive it as an interface attached to a Behaviour, or check the concrete type |
IShape s = new Box(); s == null | Comparing an interface implemented on a value type with null | TUKI0001 | by design | Receive it as an interface attached to a Behaviour (a value type’s value is never null) |
IShape s = new Box(); s?.Area() | Using ?. on an interface attached to a value type | TUKI0001 | by design | Same as above |
| Where it can live | Internal representation | |
|---|---|---|
| An interface on a value type | Local variables, parameters, return values, arrays. Not fields | The same form as a union: one number that shows which type is currently held, plus one heap variable per type. This is why the forms that don’t compile line up with union’s |
| An interface on a Behaviour | It can live in fields and arrays, and can be compared against null | It refers to the Behaviour itself |
Declaring interfaces
Section titled “Declaring interfaces”Declaring and implementing an interface
Section titled “Declaring and implementing an interface”Only method and property declarations can go in an interface.
using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceDeclare : TsukimiBehaviour{ void Start() { IShape s = new Box(); Debug.Log(s.Area()); // => 1 }}Interface properties
Section titled “Interface properties”using UnityEngine;using Tsukimi;
public interface IValue{ int Id { get; }}
public struct Num : IValue{ public int Id { get; set; }}
public class IfProperty : TsukimiBehaviour{ void Start() { IValue v = new Num { Id = 7 }; Debug.Log(v.Id); // => 7 }}Implementing a computed property
Section titled “Implementing a computed property”A value type’s implementation may be a property with a body.
using UnityEngine;using Tsukimi;
public interface IValue{ int Id { get; }}
public struct Num : IValue{ public int Id { get { return 1; } }}
public class R_props : TsukimiBehaviour{ void Start() { IValue v = new Num(); Debug.Log(v.Id); // => 1 }}Interface inheritance
Section titled “Interface inheritance”using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public interface IFast : IGadget{ void Boost();}
public struct Runner : IFast{ public void Tick() { Debug.Log(1); } // => 1 public void Boost() { Debug.Log(2); } // => 2}
public class IfaceInherit : TsukimiBehaviour{ void Start() { IFast f = new Runner(); f.Tick(); f.Boost(); }}Implementing interfaces
Section titled “Implementing interfaces”Implementation by a value type
Section titled “Implementation by a value type”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceStructImplements : TsukimiBehaviour{ void Start() { Box b = new Box(); IShape s = b; Debug.Log(s.Area()); // => 1 }}Implementation by a record
Section titled “Implementation by a record”using UnityEngine;using Tsukimi;
public interface ILabel{ int Id();}
public record Tag(int N) : ILabel{ public int Id() { return N; }}
public class IfaceRecordImplements : TsukimiBehaviour{ void Start() { ILabel l = new Tag(1); Debug.Log(l.Id()); // => 1 }}Implementation by a Behaviour
Section titled “Implementation by a Behaviour”A Behaviour can implement an interface too.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceBehaviourImplements : TsukimiBehaviour, IGadget{ public void Tick() { Debug.Log(1); }
void Start() { IGadget g = this; g.Tick(); }}Multiple implementations by a value type
Section titled “Multiple implementations by a value type”using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public interface IReset{ void Reset();}
public struct Both : IGadget, IReset{ public void Tick() { Debug.Log(1); } // => 1 public void Reset() { Debug.Log(2); } // => 2}
public class IfaceStructMultipleInterfaces : TsukimiBehaviour{ void Start() { Both b = new Both(); IGadget g = b; IReset r = b; g.Tick(); r.Reset(); }}Multiple implementations by a Behaviour
Section titled “Multiple implementations by a Behaviour”A Behaviour can also implement multiple interfaces.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public interface IReset{ void Reset();}
public class IfaceBehaviourMultipleInterfaces : TsukimiBehaviour, IGadget, IReset{ public void Tick() { Debug.Log(1); } // => 1
public void Reset() { Debug.Log(2); } // => 2
void Start() { Tick(); Reset(); }}Holding as a value
Section titled “Holding as a value”Holding in a local variable
Section titled “Holding in a local variable”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceLocalVariable : TsukimiBehaviour{ void Start() { IShape s = new Box(); Debug.Log(s.Area()); // => 1 }}Receiving as a parameter
Section titled “Receiving as a parameter”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceParameter : TsukimiBehaviour{ void Start() { Use(new Box()); }
private void Use(IShape s) { Debug.Log(s.Area()); // => 1 }}Returning as a return value
Section titled “Returning as a return value”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceReturnValue : TsukimiBehaviour{ void Start() { IShape s = Make(); Debug.Log(s.Area()); // => 1 }
private IShape Make() { return new Box(); }}Storing a value type in an interface array
Section titled “Storing a value type in an interface array”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceArrayOfValues : TsukimiBehaviour{ void Start() { IShape[] all = new IShape[2]; all[0] = new Box(); all[1] = new Box(); float total = 0f; for (int i = 0; i < all.Length; i++) { total += all[i].Area(); } Debug.Log(total); // => 2 }}Holding in an interface-typed field
Section titled “Holding in an interface-typed field”An interface attached to a Behaviour can be held in a field.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceBehaviourField : TsukimiBehaviour, IGadget{ public IGadget other;
public void Tick() { }
void Start() { other.Tick(); }}Type-testing against another interface
Section titled “Type-testing against another interface”On an interface attached to a Behaviour, you can test whether it implements another interface.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public interface IReset{ void Reset();}
public class R_isc : TsukimiBehaviour, IGadget{ public IGadget other;
public void Tick() { }
void Start() { bool b = other is IReset; Debug.Log(b); }}Holding in an interface-typed array
Section titled “Holding in an interface-typed array”Drop in multiple Behaviours from the Inspector and loop over them in one pass.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceBehaviourArray : TsukimiBehaviour, IGadget{ public IGadget[] all;
public void Tick() { }
void Start() { for (int i = 0; i < all.Length; i++) { all[i].Tick(); } }}Getting and null
Section titled “Getting and null”Getting a component by interface
Section titled “Getting a component by interface”Specify an interface as the type argument to get a Behaviour that implements it.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceGetComponent : TsukimiBehaviour, IGadget{ public void Tick() { }
void Start() { IGadget g = GetComponent<IGadget>(); if (g != null) { g.Tick(); } }}Comparing with null
Section titled “Comparing with null”This works only for an interface attached to a Behaviour.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceCompareNull : TsukimiBehaviour, IGadget{ public IGadget other;
public void Tick() { }
void Start() { Debug.Log(other == null); // => true if (other != null) { other.Tick(); } }}Null-conditional call
Section titled “Null-conditional call”?. also works only for an interface attached to a Behaviour.
using UnityEngine;using Tsukimi;
public interface IGadget{ void Tick();}
public class IfaceNullConditional : TsukimiBehaviour, IGadget{ public IGadget other;
public void Tick() { }
void Start() { other?.Tick(); }}Branching by type
Section titled “Branching by type”Testing for a concrete type
Section titled “Testing for a concrete type”Tests whether it is a concrete type.
using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceIsConcrete : TsukimiBehaviour{ void Start() { IShape s = new Box(); bool isBox = s is Box; Debug.Log(isBox); // => true }}Branching on a concrete type
Section titled “Branching on a concrete type”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public struct Dot : IShape{ public float Area() { return 0f; }}
public class IfaceSwitchTypePattern : TsukimiBehaviour{ void Start() { IShape s = new Box(); float a = s switch { Box b => b.Area(), Dot d => d.Area(), _ => 0f, }; Debug.Log(a); // => 1 }}Branching without a default case
Section titled “Branching without a default case”When the cases cover every implementing type, no default case is needed.
using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public struct Dot : IShape{ public float Area() { return 0f; }}
public class IfaceSwitchExhaustive : TsukimiBehaviour{ void Start() { IShape s = new Box(); float a = s switch { Box b => b.Area(), Dot d => d.Area(), }; Debug.Log(a); // => 1 }}Generics
Section titled “Generics”Constraining a type argument by interface
Section titled “Constraining a type argument by interface”using UnityEngine;using Tsukimi;
public interface IShape{ float Area();}
public struct Box : IShape{ public float Area() { return 1f; }}
public class IfaceGenericConstraint : TsukimiBehaviour{ void Start() { Debug.Log(Measure(new Box())); // => 1 }
private float Measure<T>(T shape) where T : IShape { return shape.Area(); }}