{"id":21306067,"url":"https://github.com/chickensoft-games/introspection","last_synced_at":"2025-04-07T05:10:35.339Z","repository":{"id":242647204,"uuid":"810110866","full_name":"chickensoft-games/Introspection","owner":"chickensoft-games","description":"Create mixins and generate metadata about types at build time to enable reflection in ahead-of-time (AOT) environments.","archived":false,"fork":false,"pushed_at":"2025-02-21T16:38:24.000Z","size":382,"stargazers_count":35,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-05T16:08:39.703Z","etag":null,"topics":["csharp","introspection","mixins","reflection","sourcegenerator"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages?q=chickensoft.introspection","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chickensoft-games.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-04T04:22:25.000Z","updated_at":"2025-02-22T18:44:46.000Z","dependencies_parsed_at":"2024-06-04T07:50:06.652Z","dependency_job_id":"b1bce825-98c7-4d31-be20-bbce8693132f","html_url":"https://github.com/chickensoft-games/Introspection","commit_stats":null,"previous_names":["chickensoft-games/introspection"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chickensoft-games%2FIntrospection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chickensoft-games%2FIntrospection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chickensoft-games%2FIntrospection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chickensoft-games%2FIntrospection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chickensoft-games","download_url":"https://codeload.github.com/chickensoft-games/Introspection/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247595335,"owners_count":20963943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","introspection","mixins","reflection","sourcegenerator"],"created_at":"2024-11-21T16:21:14.986Z","updated_at":"2025-04-07T05:10:35.318Z","avatar_url":"https://github.com/chickensoft-games.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔮 Introspection\n\n[![Chickensoft Badge][chickensoft-badge]][chickensoft-website] [![Discord][discord-badge]][discord] [![Read the docs][read-the-docs-badge]][docs] ![line coverage][line-coverage] ![branch coverage][branch-coverage]\n\nCreate mixins and generate metadata about types at build time to enable reflection in ahead-of-time (AOT) environments.\n\n---\n\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"Chickensoft.Introspection\" src=\"Chickensoft.Introspection/icon.png\" width=\"200\"\u003e\n\u003c/p\u003e\n\n## 🥚 Installation\n\nFind the latest version of the [Introspection] and [Introspection Generator] packages from nuget and add them to your C# project.\n\n\u003e [!WARNING]\n\u003e Introspection is compiled against the latest .NET 8 SDK. Because .NET 8 encompasses multiple versions of the [.NET compiler], compiling your project against an older version of .NET 8 may generate a `CS9057` warning to indicate a compiler-version mismatch. However, if the warning is ignored, less-tractable downstream compilation errors, or even runtime errors, may result. Therefore, we strongly recommend treating `CS9057` as an error to catch compiler versioning issues at the earliest opportunity.\n\n```xml\n\u003cProject Sdk=...\u003e\n  \u003cPropertyGroup\u003e\n    \u003cTargetFramework\u003enet8.0\u003c/TargetFramework\u003e\n    ...\n    \u003c!-- Catch compiler-mismatch issues --\u003e\n    \u003cWarningsAsErrors\u003eCS9057\u003c/WarningsAsErrors\u003e\n    ...\n  \u003c/PropertyGroup\u003e\n\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"Chickensoft.Introspection\" Version=... /\u003e\n    \u003cPackageReference Include=\"Chickensoft.Introspection.Generator\" Version=... PrivateAssets=\"all\" OutputItemType=\"analyzer\" /\u003e\n  \u003c/ItemGroup\u003e\n\u003c/Project\u003e\n```\n\n## 📙 Background\n\nThis is a metaprogramming tool that powers some of the other Chickensoft tools. Introspection exists to enable C# developers to get information about a type at runtime without needing reflection that wouldn't work or be guaranteed to work in all scenarios when compiling for ahead-of-time targets like iOS.\n\nThe introspection package provides the following features:\n\n- ✅ Create a registry of all types visible from the global scope.\n- ✅ Generate metadata about visible types.\n- ✅ Track types by id and version.\n- ✅ Allow types to implement and look up mixins.\n- ✅ Compute and cache type hierarchies, attributes, and properties.\n- ✅ Track generic types of properties in a way that enables convenient serialization in AOT environments.\n\nYou don't need to fully understand this package to make the most of it. In fact, you may never need to use it directly since you are more likely to encounter it as a dependency of one of the other Chickensoft tools:\n\n- 💾 [Serialization] uses this tool to gather information about types at build-time to perform serialization and deserialization at runtime without having to resort to unsupported reflection techniques in AOT environments.\n- 💉 [AutoInject] uses this tool to allow you to add mixins to classes at build-time and invoke their methods at runtime without reflection. It also leverages this to read attributes on types without having to use reflection.\n- 💡 [LogicBlocks] uses this tool to look up possible states for a state machine so it can pre-allocate them at runtime without needing reflection.\n\nThe introspection generator is designed to be performant as a project grows. The generator only uses syntax information to generate metadata, rather than relying on the C# analyzer's symbol data, which can be very slow.\n\n\u003e [!NOTE]\n\u003e This tool exists because many reflection-based API's don't work (or aren't guaranteed to work reliably in all scenarios) when compiling for AOT targets (like iOS). Chickensoft is striving to create packages that work in AOT environments like iOS, as mobile games and apps are extremely widespread. For more information, read our [philosophy].\n\n## 📄 Usage\n\n### 🧘‍♀️ Introspective Types\n\nSimply add the `[Meta]` attribute to a partial class or record that is visible from the global scope.\n\n```csharp\nusing Chickensoft.Introspection;\n\n[Meta]\npublic partial class MyType;\n\npublic partial class Container {\n  // Nested types are supported, too.\n  [Meta]\n  public partial class MyType;\n}\n```\n\nThe generator will generate a [type registry] for your assembly that lists every type it can discover in the codebase, along with their generated metadata. Introspective types have much additional metadata compared to types without the `[Meta]` attribute.\n\nThe generated registry automatically registers types with the Introspection library's [type graph] using a [module initializer], so no action is needed on the developer's part. The module initializer registration process also performs some logic at runtime to resolve the type graph and cache the type hierarchy in a way that makes it performant to lookup. This preprocessing runs in roughly linear time and is negligible.\n\nAll introspective types must be a class or record, partial, visible from the global scope. Introspective types cannot be generic.\n\n### 🪪 Identifiable Types\n\nAn introspective type can also be an identifiable type if it is given the `[Id]` attribute. Identifiable types get additional metadata generated about them, allowing them to be looked up by their identifier.\n\n```csharp\n  [Meta, Id(\"my_type\")]\n  public partial class MyType;\n```\n\n### ⤵️ The Type Graph\n\nThe type graph can be used to query information about types at runtime. If the type graph has to compute a query, the results are cached for all future queries. Most api's are simple O(1) lookups.\n\n```csharp\n\n// Get every type that is a valid subtype of Ancestor.\nvar allSubtypes = Types.Graph.GetDescendantSubtypes(typeof(Ancestor));\n\n// Only get the types that directly inherit from Parent.\nvar subtypes = Types.Graph.GetSubtypes(typeof(Parent));\n\n// Get generated metadata associated with a type.\nif (Types.Graph.GetMetadata(typeof(Model)) is { } metadata) {\n  // ...\n}\n\n// Get properties, including those from parent introspective types.\nvar properties = Types.Graph.GetProperties(typeof(Model));\n\n// ...see the source for all possible type graph operations.\n```\n\n### 👯‍♀️ Versioning\n\nAll concrete introspective types have a simple integer version associated with them. By default, the version is `1`. You can use the `[Version]` attribute to denote the version of an introspective type.\n\n```csharp\n[Meta, Version(2)]\npublic partial class MyType;\n\n// Or, multiple versions of the same identifiable type.\n\n[Meta, Id(\"my_type\")]\npublic abstract class MyType;\n\n[Meta, Version(1)]\npublic class MyType1 : MyType;\n\n[Meta, Version(2)]\npublic class MyType2 : MyType;\n\n[Meta, Version(3)]\npublic class MyType3 : MyType;\n```\n\nDuring type registration, the type graph will \"promote\" introspective types which inherit from an identifiable type to an identifiable type themselves, sharing the same identifier as their parent or ancestor identifiable type. Promoted identifiable types must, however, have uniquely specified versions.\n\n## 🔎 Metadata Types\n\nThe introspection generator differentiates between the following categories of types and constructs the appropriate metadata for the type, depending on which category it belongs to.\n\n| Category                        | Metadata                            |\n|---------------------------------|-------------------------------------|\n| 🫥 Abstract or generic types    | `TypeMetadata`                      |\n| 🪨 Non-generic, concrete types  | `ConcreteTypeMetadata`              |\n| 👻 Abstract introspective types | `AbstractIntrospectiveTypeMetadata` |\n| 🗿 Concrete introspective types | `IntrospectiveTypeMetadata`         |\n| 🆔 Abstract identifiable types  | `AbstractIdentifiableTypeMetadata`  |\n| 🪪 Concrete identifiable types  | `IdentifiableTypeMetadata`          |\n\nYou can check the type of metadata that a type has to understand what its capabilities are. Each type of metadata has different fields associated with it.\n\nIn addition to the metadata classes, each metadata class implements the appropriate interfaces:\n\n| Metadata                    | Conforms To                                     |\n|-----------------------------|-------------------------------------------------|\n| `TypeMetadata`              | `ITypeMetadata`                                 |\n| `ConcreteTypeMetadata`      | ..., `IClosedTypeMetadata`, `IConcreteMetadata` |\n| `IntrospectiveTypeMetadata` | ..., `IConcreteIntrospectiveTypeMetadata`       |\n| `IdentifiableTypeMetadata`  | ..., `IIdentifiableTypeMetadata`                |\n| ... etc.                    |                                                 |\n\n```csharp\npublic class MyTypeReceiver : ITypeReceiver {\n  public void Receive\u003cT\u003e() {\n    // Do whatever you want with the type as a generic parameter.\n  }\n}\n\nvar metadata = Types.Graph.GetMetadata(typeof(Model));\n\nif (metadata is IClosedTypeMetadata closedMetadata) {\n  // Closed types allow you to receive the type as a generic argument in\n  // a TypeReceiver's Receive\u003cT\u003e() method.\n  closedMetadata.GenericTypeGetter(new MyTypeReceiver())\n}\n\nif (metadata is IConcreteTypeMetadata concreteMetadata) {\n  // Concrete types allow you to create a new instance of the type, if\n  // it has a parameterless constructor.\n  var instance = concreteMetadata.Factory();\n}\n\nif (metadata is IIntrospectiveTypeMetadata introMetadata) {\n  // Introspective types provide a metatype instance which allows you to access\n  // more information about that type, such as its properties and attributes.\n  var metatype = introMetadata.Metatype;\n}\n\nif (metadata is IConcreteIntrospectiveTypeMetadata concreteIntroMetadata) {\n  // Concrete introspective types have a version number.\n  var version = concreteIntroMetadata.Version;\n}\n\nif (metadata is IIdentifiableTypeMetadata idMetadata) {\n  // Identifiable types have an id.\n  var id = idMetadata.Id;\n}\n```\n\n## Δ Metatypes\n\nThe introspection generator generates additional metadata for introspective and identifiable types known as a \"metatype.\" A type's metatype information can be accessed from its metadata.\n\n```csharp\nvar metadata = Types.Graph.GetMetadata(typeof(Model));\n\nif (metadata is IIntrospectiveTypeMetadata introMetadata) {\n  var metatype = introMetadata.Metatype;\n\n  foreach (var attribute in metatype.Attributes) {\n    // Iterate the attributes on an introspective type.\n  }\n\n  foreach (var property in metatype.Properties) {\n    // Iterate the properties of an introspective type.\n    if (property.Setter is { } setter) {\n      // We can set the value of the property.\n      setter(obj, value);\n    }\n\n    // etc.\n  }\n}\n```\n\nMetatype data provides information about a specific type, its properties, and attributes. The type graph combines metatype information with its understanding of the type hierarchy to enable you to fetch all properties of an introspective type, including those it inherited from other introspective types. Metatypes will only contain information about the type itself, not anything it inherits from.\n\nTo see all of the information that a metatype exposes, please see the [Metatype interface definition][metatype].\n\n## 🎛️ Mixins\n\nThe introspection generator allows you to create mixins to add additional functionality to the type they are applied to. Unlike [default interface method implementations], mixins are able to add _[instance state]_ via a [blackboard]. Every introspective type has a `MixinState` blackboard which allows mixins to add instance data to the type they are applied to.\n\nAdditionally, mixins must implement a single handler method. An introspective type's `Metatype` has a `Mixins` property containing a list of mixin types that were applied to it. Additionally, a `MixinHandler` table is provided which maps the mixin type to a closure which invokes the mixin's handler.\n\nIntrospective type instances can also cast themselves to `IIntrospective` to invoke a given mixin easily.\n\n```csharp\n// Declare a mixin\n[Mixin]\npublic interface IMyMixin : IMixin\u003cIMyMixin\u003e {\n  void IMixin\u003cIMyMixin\u003e.Handler() { }\n}\n\n// Use a mixin\n[Meta(typeof(Mixin))]\npublic partial class MyModel {\n\n  // Use mixins\n  public void MyMethod() {\n    // Call all applied mixin handlers\n    (this as IIntrospective).InvokeMixins();\n\n    // Call a specific mixin handler\n    (this as IIntrospective).InvokeMixin(typeof(IMyMixin));\n  }\n}\n```\n\n---\n\n🐣 Package generated from a 🐤 Chickensoft Template — \u003chttps://chickensoft.games\u003e\n\n[chickensoft-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/chickensoft_badge.svg\n[chickensoft-website]: https://chickensoft.games\n[philosophy]: https://chickensoft.games/philosophy\n[discord-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/discord_badge.svg\n[discord]: https://discord.gg/gSjaPgMmYW\n[read-the-docs-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/read_the_docs_badge.svg\n[docs]: https://chickensoft.games/docs/\n[line-coverage]: Chickensoft.Introspection.Tests/badges/line_coverage.svg\n[branch-coverage]: Chickensoft.Introspection.Tests/badges/branch_coverage.svg\n\n[Introspection]: https://www.nuget.org/packages/Chickensoft.Introspection\n[Introspection Generator]: https://www.nuget.org/packages/Chickensoft.Introspection.Generator\n[Serialization]: https://github.com/chickensoft-games/Serialization\n[AutoInject]: https://github.com/chickensoft-games/AutoInject\n[LogicBlocks]: https://github.com/chickensoft-games/LogicBlocks\n[type registry]: Chickensoft.Introspection.Generator.Tests/.generated/Chickensoft.Introspection.Generator/Chickensoft.Introspection.Generator.TypeGenerator/TypeRegistry.g.cs\n[type graph]: Chickensoft.Introspection/src/TypeGraph.cs\n[module initializer]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers\n[default interface method implementations]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods\n[instance state]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods#detailed-design\n[blackboard]: https://github.com/chickensoft-games/Collections?tab=readme-ov-file#blackboard\n[metatype]: Chickensoft.Introspection/src/types/IMetatype.cs\n[.NET compiler]: https://github.com/dotnet/roslyn/blob/main/docs/wiki/NuGet-packages.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchickensoft-games%2Fintrospection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchickensoft-games%2Fintrospection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchickensoft-games%2Fintrospection/lists"}