<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.themacaque.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.themacaque.com/" rel="alternate" type="text/html" /><updated>2026-03-02T00:23:26+00:00</updated><id>https://www.themacaque.com/feed.xml</id><title type="html">Le Lotus bleu</title><subtitle>Welcome to MySpace!</subtitle><author><name>Manuel de la Peña Saenz</name></author><entry><title type="html">git-memento: attaching AI session transcripts to Git commits (without changing commit hashes)</title><link href="https://www.themacaque.com/2026/03/01/memento-intro.html" rel="alternate" type="text/html" title="git-memento: attaching AI session transcripts to Git commits (without changing commit hashes)" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://www.themacaque.com/2026/03/01/memento-intro</id><content type="html" xml:base="https://www.themacaque.com/2026/03/01/memento-intro.html"><![CDATA[<p>We’re starting to merge AI-generated code into production.</p>

<p>But we rarely preserve the thing that actually produced it: the session.</p>

<p>Six months later, when someone asks:</p>

<blockquote>
  <p>“Why was this implemented this way?”<br />
“What constraints did we give the model?”<br />
“Was this a deliberate tradeoff or just the first suggestion?”</p>
</blockquote>

<p>… the only artifact left is the diff.</p>

<p><code>git-memento</code> makes AI-assisted commits auditable using Git notes.</p>

<p>It does not rewrite commit history.<br />
It does not modify commit hashes.<br />
It does not stuff transcripts into commit messages.</p>

<p>It attaches the session transcript as a Git note.</p>

<hr />

<h2 id="the-idea">The idea</h2>

<p>Git already supports attaching metadata to commits via <code>refs/notes/*</code>.</p>

<pre><code class="language-bash">git notes add -m "AI session: codex abc123" &lt;commit&gt;
git notes show &lt;commit&gt;
</code></pre>

<p>Notes:</p>
<ul>
  <li>Don’t change commit hashes.</li>
  <li>Can be pushed/fetched.</li>
  <li>Are versioned like other refs.</li>
  <li>Survive rebases if configured correctly.</li>
</ul>

<p><code>git-memento</code> standardizes this pattern for AI-assisted development.</p>

<hr />

<h2 id="what-it-actually-does">What it actually does</h2>

<p>When you run:</p>

<pre><code class="language-bash">git memento commit &lt;session-id&gt; -m "feat: add idempotency key"
</code></pre>

<p>It:</p>

<ol>
  <li>Runs <code>git commit</code> normally.</li>
  <li>Resolves <code>HEAD</code>.</li>
  <li>Fetches the AI session transcript.</li>
  <li>Attaches it via:
    <pre><code class="language-bash">git notes add -f -m &lt;rendered-markdown&gt; &lt;commit&gt;
</code></pre>
  </li>
</ol>

<p>That’s it.</p>

<p>The commit hash stays the same.</p>

<hr />

<h2 id="why-this-matters">Why this matters</h2>

<p>AI makes it easy to produce code quickly.</p>

<p>It also makes it easy to lose context quickly.</p>

<p>Attaching the session transcript directly to the commit helps with:</p>

<ul>
  <li>
    <p><strong>Incident debugging</strong><br />
You can inspect the conversation that produced the code.</p>
  </li>
  <li>
    <p><strong>Code review</strong><br />
Reviewers can see intent and constraints, not just output.</p>
  </li>
  <li>
    <p><strong>Compliance / governance</strong><br />
You can demonstrate provenance for machine-assisted changes.</p>
  </li>
  <li>
    <p><strong>Long-term maintainability</strong><br />
Context lives with the commit, not in Slack or a browser tab.</p>
  </li>
</ul>

<p>This is not about policing developers.</p>

<p>It’s about preserving engineering context at the point where history is already recorded: the commit.</p>

<hr />

<h2 id="ci-enforcement">CI enforcement</h2>

<p>You can enforce note coverage in pull requests:</p>

<pre><code class="language-bash">git memento audit --range origin/main..HEAD --strict
</code></pre>

<p>Fail if:</p>
<ul>
  <li>A commit is missing a note.</li>
  <li>A note exists but doesn’t match the expected structure.</li>
</ul>

<p>There’s also a GitHub Action that:</p>
<ul>
  <li>Syncs notes</li>
  <li>Fails PRs without provenance</li>
  <li>Optionally renders note content as commit comments</li>
</ul>

<hr />

<h2 id="history-rewrite-support">History rewrite support</h2>

<p>Notes can survive rebases and amendments via:</p>

<pre><code class="language-bash">git memento notes-rewrite-setup
</code></pre>

<p>There are also commands for:</p>
<ul>
  <li>Syncing notes across collaborators</li>
  <li>Backing up note refs before merges</li>
  <li>Carrying provenance across squash flows</li>
</ul>

<p>All built on top of normal Git primitives.</p>

<hr />

<h2 id="installation">Installation</h2>

<pre><code class="language-bash">curl -fsSL https://raw.githubusercontent.com/mandel-macaque/memento/main/install.sh | sh
</code></pre>

<p>Repository:</p>

<ul>
  <li>https://github.com/mandel-macaque/memento</li>
</ul>

<hr />

<h2 id="design-philosophy">Design philosophy</h2>

<p>This is intentionally boring.</p>

<p>No database.<br />
No external service.<br />
No commit rewriting.<br />
No hidden state.</p>

<p>Just Git notes used deliberately.</p>

<p>If AI contributes to a commit, the session should be part of the artifact.</p>

<p><code>git-memento</code> makes that practical.</p>]]></content><author><name>Manuel de la Peña Saenz</name></author><category term="Other" /><summary type="html"><![CDATA[We’re starting to merge AI-generated code into production.]]></summary></entry><entry><title type="html">Objective-C vs C#: A Conceptual Primer for Bindings</title><link href="https://www.themacaque.com/2026/01/27/objc-vs-csharp.html" rel="alternate" type="text/html" title="Objective-C vs C#: A Conceptual Primer for Bindings" /><published>2026-01-27T00:00:00+00:00</published><updated>2026-01-27T00:00:00+00:00</updated><id>https://www.themacaque.com/2026/01/27/objc-vs-csharp</id><content type="html" xml:base="https://www.themacaque.com/2026/01/27/objc-vs-csharp.html"><![CDATA[<p>In the previous post I explained how we can design an SDK, that with the help
of code generators and analyzer, could behave like a DSL (Domain Specific Language).
In this article I am going to focus on given the needed background to understand what
it means to create a binding generator from Objc to C#</p>

<p>When creating bindings between Objective-C and C#, you’re not just translating syntax—you’re bridging two runtimes with
very different philosophies. Those differences show up most clearly in memory management, method dispatch, and abstraction
mechanisms (protocols vs interfaces). Understanding these mismatches is critical to producing correct, safe, and idiomatic
bindings.</p>

<h2 id="objects-and-memory-management-arc-vs-garbage-collection">Objects and Memory Management: ARC vs Garbage Collection</h2>

<h3 id="objective-c-reference-counting-with-arc">Objective-C: Reference Counting with ARC</h3>

<p><em>Objective-C</em> uses reference counting as its fundamental memory management model. Modern Objective-C relies on ARC 
(Automatic Reference Counting), which inserts retain, release, and autorelease calls at compile time.</p>

<p><em>Key points</em></p>

<ul>
  <li>Every object has a retain count.</li>
  <li>Ownership matters.</li>
  <li>Object lifetimes are deterministic.</li>
</ul>

<pre><code class="language-objectivec">NSObject *obj = [[NSObject alloc] init]; // retain count +1
self.property = obj;                     // retain
[obj release];                            // release
</code></pre>

<p>ARC removes the need to write these calls manually, but the <em>ownership rules still exist:</em></p>

<ul>
  <li><em>strong / retain:</em> keeps the object alive</li>
  <li><em>weak:</em> does not retain, automatically zeroed when the object deallocates</li>
  <li><em>assign:</em> unsafe for object references (no zeroing)</li>
</ul>

<p>This ownership rules leak to the API design:</p>

<pre><code class="language-objectivec">@property (nonatomic, strong) NSObject *owner;
@property (nonatomic, weak) NSObject *delegate;
</code></pre>

<p>This matters when designing an API in objc because:</p>

<ul>
  <li>Cycles (strong ↔ strong) leak memory.</li>
  <li>Delegates must be weak by convention.</li>
</ul>

<h3 id="c-garbage-collection">C#: Garbage Collection</h3>

<p>C# uses a tracing garbage collector, we might have long conversations on why this is a
better or worse approach; or you can be like python, and do both ;)</p>

<ul>
  <li>Objects are allocated on the managed heap.</li>
  <li>Memory is reclaimed non-deterministically.</li>
  <li>Developers generally don’t think about object lifetimes.</li>
</ul>

<pre><code class="language-csharp">var obj = new object();
// No retain/release, GC handles cleanup
</code></pre>

<p>From the above, the important details we need to understand are:</p>

<ul>
  <li>C# references are always strong by default.</li>
  <li>WeakReference&lt;T&gt; exists but is explicit and rare.</li>
  <li>Cycles are not a problem (most of the time).</li>
</ul>

<p>The following table shows the mismatch between the two and what we 
have to be careful about:</p>

<table>
  <thead>
    <tr>
      <th>Concept</th>
      <th>Objective-C</th>
      <th>C#</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lifetime</td>
      <td>Deterministic</td>
      <td>Non-deterministic</td>
    </tr>
    <tr>
      <td>Cycles</td>
      <td>Leak</td>
      <td>Collected</td>
    </tr>
    <tr>
      <td>Weak refs</td>
      <td>Built-in &amp; common</td>
      <td>Explicit &amp; uncommon</td>
    </tr>
    <tr>
      <td>Destruction</td>
      <td><code>dealloc</code></td>
      <td>Finalizers / <code>IDisposable</code></td>
    </tr>
  </tbody>
</table>

<p>When binding ObjC to C#, you must:</p>

<ul>
  <li>Preserve ObjC ownership semantics.</li>
  <li>Prevent premature deallocation of native objects.</li>
  <li>Avoid leaking native objects retained by managed references.</li>
</ul>

<h2 id="method-resolution-message-passing-vs-method-calls">Method Resolution: Message Passing vs Method Calls</h2>

<h3 id="objective-c-message-passing">Objective-C: Message Passing</h3>

<p>In Objective-C, methods are not called—they are messages sent at <em>runtime</em>.</p>

<pre><code class="language-objectivec">[id object doSomething];
</code></pre>

<p>If we <a href="https://developer.apple.com/documentation/objectivec">simplify</a> the above a lot that can be translated to:</p>

<pre><code class="language-objectivec"> objc_msgSend(object, @selector(doSomething));
</code></pre>

<p>Consequences:</p>

<ul>
  <li>Method resolution happens at runtime.</li>
  <li>Objects can respond to selectors dynamically.</li>
  <li>Messages to nil are legal and do nothing.</li>
  <li>Method implementations can be swapped at runtime.</li>
</ul>

<p>We can consider Objc to be a dynamic language more like Python or Smalltalk (or C like some smart people can argue)</p>

<h3 id="c-static-method-dispatch-mostly">C#: Static Method Dispatch (Mostly)</h3>

<p>C# uses compile-time method resolution, with runtime dispatch only for:</p>

<ul>
  <li><em>virtual</em> methods</li>
  <li>Interface calls</li>
  <li>Reflection / <em>dynamic</em></li>
</ul>

<pre><code class="language-csharp">obj.DoSomething(); // must exist at compile time
</code></pre>

<p>We can summarize this as <em>“If it does not exist, it does not compile”</em>. This a <em>HUGE</em> simplification,
virtual methods, interfaces and dynamic work in a diff way, they are useful but come with a cost.</p>

<h3 id="a-classic-objc-gotcha-for-bindings">A Classic ObjC Gotcha for Bindings</h3>

<p>Apple often returns objects that behave like a class but aren’t actually that class, and you can see a lot
of this cases in the Microsoft.iOS bindings, I have had several issues with the NSData object and the NSUrlSession</p>

<pre><code class="language-objectivec">NSString *s = [someApi returnsAString];
NSLog(@"%@", [s class]); // might be __NSCFString
</code></pre>

<p>This is legal because:</p>

<ul>
  <li>ObjC relies on behavior, not concrete types.</li>
  <li>Class clusters are common (NSString, NSArray, NSNumber).</li>
</ul>

<p>The fact that Apple does this is of a lot of importance in our API design. Bindings must:</p>

<ul>
  <li>Avoid assuming concrete native types.</li>
  <li>Prefer base classes or protocols.</li>
  <li>Be defensive around casting.</li>
</ul>

<h2 id="protocols-vs-interfaces">Protocols vs Interfaces</h2>

<h3 id="objective-c-protocols">Objective-C Protocols</h3>

<p>Protocols define a contract of behavior, not inheritance. This is very heavily used and pushed in Swift
with the concept of <a href="https://www.hackingwithswift.com/sixty/9/5/protocol-oriented-programming">protocol oriented programming</a>.</p>

<pre><code class="language-objectivec">@protocol MyProtocol &lt;NSObject&gt;
@required
- (void)requiredMethod;

@optional
- (void)optionalMethod;
@end
</code></pre>

<p>Features:</p>

<ul>
  <li>Methods can be optional</li>
  <li>Checked at runtime via respondsToSelector:</li>
  <li>Used heavily for delegation</li>
</ul>

<pre><code class="language-objectivec">if ([delegate respondsToSelector:@selector(optionalMethod)]) {
    [delegate optionalMethod];
}
</code></pre>

<h3 id="c-interfaces">C# Interfaces</h3>

<p>Traditionally, C# interfaces:</p>

<ul>
  <li>Required all members to be implemented</li>
  <li>Were purely abstract (changes were added thanks to the Microsoft.iOS folks)</li>
</ul>

<pre><code class="language-csharp">public interface IMyInterface
{
    void RequiredMethod();
}
</code></pre>

<p>This made binding ObjC protocols awkward, especially when optional methods were involved. The old
Xamarin API had to work around this situation when generating code and that is why the API for
protocols is kind of complicated (we will look at the old API in a future post).</p>

<p>C# 8.0 introduced default implementations, partially closing the gap:</p>

<pre><code class="language-csharp">public interface IMyInterface
{
    void RequiredMethod();

    void OptionalMethod()
    {
        // default behavior
    }
}
</code></pre>

<p>This was added to fix a few issues:</p>

<ul>
  <li>Allow SDKs to introduce new methods and not change API</li>
  <li>Help with Xamarin.iOS</li>
</ul>

<p>The following table simplifies the differences:</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>ObjC Protocol</th>
      <th>C# Interface</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Optional methods</td>
      <td>Yes</td>
      <td>No (until defaults)</td>
    </tr>
    <tr>
      <td>Runtime checks</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Duck typing</td>
      <td>Common</td>
      <td>Rare</td>
    </tr>
    <tr>
      <td>Delegation</td>
      <td>Idiomatic</td>
      <td>Less idiomatic</td>
    </tr>
  </tbody>
</table>

<h2 id="closing-why-this-matters-for-bindings">Closing: Why This Matters for Bindings</h2>

<p>Objective-C and C# are both “object-oriented,” but they express that idea very differently:</p>

<ul>
  <li>ObjC favors runtime flexibility</li>
  <li>C# favors compile-time safety</li>
</ul>

<p>When creating bindings, you are translating:</p>

<ul>
  <li>Deterministic lifetimes → GC</li>
  <li>Message passing → method calls</li>
  <li>Behavioral typing → nominal typing</li>
</ul>

<p>Understanding these differences upfront helps avoid:</p>

<ul>
  <li>Memory leaks and crashes
= Invalid type assumptions
= Fragile or unidiomatic APIs on the C# side</li>
</ul>

<p>In short: good bindings don’t just compile—they respect the semantics of both worlds. If you
think about it, Roslyn analyzer make perfect sense to allow the introduction of new semantics
to the C# language. Enforcing these new semantics allows to create better and more idiomatic bindings
with the safety net of a compile time check</p>]]></content><author><name>Manuel de la Peña Saenz</name></author><category term="Other" /><summary type="html"><![CDATA[In the previous post I explained how we can design an SDK, that with the help of code generators and analyzer, could behave like a DSL (Domain Specific Language). In this article I am going to focus on given the needed background to understand what it means to create a binding generator from Objc to C#]]></summary></entry><entry><title type="html">Creating a Domain Specific Language with Roslyn</title><link href="https://www.themacaque.com/2026/01/17/dsl-with-roslyn.html" rel="alternate" type="text/html" title="Creating a Domain Specific Language with Roslyn" /><published>2026-01-17T00:00:00+00:00</published><updated>2026-01-17T00:00:00+00:00</updated><id>https://www.themacaque.com/2026/01/17/dsl-with-roslyn</id><content type="html" xml:base="https://www.themacaque.com/2026/01/17/dsl-with-roslyn.html"><![CDATA[<p>I left Microsoft three months ago. During that time, I realized that very few people—both inside and outside the
company—understood what the long-term goals of the Microsoft.iOS and Microsoft.MacOS projects actually were.</p>

<p>This post is the first in a series where I’ll explain how, while working on Microsoft.iOS, I came to see iOS bindings 
not as a code-generation problem, but as a language design problem. More specifically, how we used Roslyn to turn 
binding definitions into a Domain Specific Language (DSL)—with real syntax, real semantics, and compile-time guarantees.</p>

<p>This series uses Microsoft.iOS as a concrete example, but the ideas apply to anyone building SDKs, bindings, 
or large code-generated APIs.</p>

<h2 id="what-is-a-domain-specific-language">What is a Domain Specific Language?</h2>

<p>A <a href="https://en.wikipedia.org/wiki/Domain-specific_language">Domain Specific Language</a> is a programming language designed
to solve a specific problem, in contrast to a general-purpose language like C#.</p>

<p>This idea is closely related to <a href="https://en.wikipedia.org/wiki/Language-oriented_programming">language-oriented programming</a>: 
instead of forcing a general language to model a domain, you design language constructs that reflect the domain itself.</p>

<h3 id="so-what-does-that-mean-for-ios">So what does that mean for iOS?</h3>

<p>The goal of Xamarin.iOS, and later Microsoft.iOS, was to fully expose Objective-C APIs to C# developers. To do that 
correctly, we needed more than wrappers. We needed a way to describe Objective-C APIs in C#, while preserving:</p>

<ul>
  <li>ABI details</li>
  <li>Memory semantics</li>
  <li>Platform availability</li>
  <li>Objective-C–specific constraints that don’t exist in C#</li>
</ul>

<p>In other words, we needed a DSL that defines the contract between Objective-C and C#.</p>

<h2 id="what-is-roslyn">What is Roslyn?</h2>

<p>Roslyn is a compiler framework that allows you to write code that can be used to generate code. The way I view Roslyn is
a set of APIs that allows me to be part of the compiler pipeline, and that way modify its behavior to match that one
of the domain I am interested in. I am going to oversimplify how Roslyn works and focus on the parts that are relevant
to our goal, but if you are interested in learning more about Roslyn I recommend reading the 
<a href="https://github.com/dotnet/roslyn/blob/384ccc39a5733c54fc6dd2542575d57dc32e7586/docs/features/incremental-generators.md">Roslyn documentation</a>.</p>

<p>For our project we are going to use the three extension points that Roslyn provides:</p>

<ul>
  <li>Roslyn code generators (incremental generators)</li>
  <li>Roslyn analyzers (syntax and semantic analyzers)</li>
  <li>Roslyn code fix providers (quick fixes)</li>
</ul>

<p>The above list provides enough entry points to create a Domain Specific Language for iOS bindings and probably more. Let’s
focus on what we are planning to do with each of these entry points.</p>

<h2 id="roslyn-code-generators-incremental-generators">Roslyn code generators (incremental generators)</h2>

<p>Bindings are very repetitive, most of the code looks the same, and they can be easily automated. Most of the work of 
a binding is to work around the impedance mismatch between Objective-C and C#. That is, the Objective-C APIs are not 
as expressive as C# and the C# APIs are not as expressive as Objective-C and we need to work around that.</p>

<p>If you open any of the generated bindings from the Microsoft.iOS project you will notice that they are very repetitive.
The majority of the code is the same, and looks like this:</p>

<pre><code class="language-csharp">[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("maccatalyst13.1")]
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
public static partial global::Foundation.NSCharacterSet Alphanumerics
{
    [SupportedOSPlatform ("macos")]
    [SupportedOSPlatform ("ios")]
    [SupportedOSPlatform ("tvos")]
    [SupportedOSPlatform ("maccatalyst13.1")]
    [Export ("alphanumericCharacterSet")]
    get
    {
        global::Foundation.NSCharacterSet ret;
        if (IsDirectBinding) {
            ret = global::ObjCRuntime.Runtime.GetNSObject&lt;global::Foundation.NSCharacterSet&gt; (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSend (this.Handle, global::ObjCRuntime.Selector.GetHandle ("alphanumericCharacterSet")))!;
        } else {
            ret = global::ObjCRuntime.Runtime.GetNSObject&lt;global::Foundation.NSCharacterSet&gt; (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSendSuper (this.SuperHandle, global::ObjCRuntime.Selector.GetHandle ("alphanumericCharacterSet")))!;
        }
        global::System.GC.KeepAlive (this);
        MarkDirty ();
        __mt_Alphanumerics_var_static = ret;
        return ret;
    }

    [SupportedOSPlatform ("macos")]
    [SupportedOSPlatform ("ios")]
    [SupportedOSPlatform ("tvos")]
    [SupportedOSPlatform ("maccatalyst13.1")]
    [Export ("setAlphanumericCharacterSet:")]
    set
    {
        var value__handle__ = value!.GetNonNullHandle (nameof (value));
        if (IsDirectBinding) {
            global::ObjCRuntime.Messaging.void_objc_msgSend_NativeHandle (this.Handle, global::ObjCRuntime.Selector.GetHandle ("setAlphanumericCharacterSet:"), value__handle__);
        } else {
            global::ObjCRuntime.Messaging.void_objc_msgSendSuper_NativeHandle (this.SuperHandle, global::ObjCRuntime.Selector.GetHandle ("setAlphanumericCharacterSet:"), value__handle__);
        }
        global::System.GC.KeepAlive (this);
        global::System.GC.KeepAlive (value);
        MarkDirty ();
        __mt_Alphanumerics_var_static = value;
    }
}
</code></pre>

<p>The above code is generated for every Objective-C API exposed in the bindings. The example above is for the 
<code>alphanumericCharacterSet</code> property, and most of the code focuses on converting C# types to Objective-C types and vice
versa. It also makes sure that the memory management is correct, as Objective-C APIs are reference counted while C# 
APIs are not. Memory could be a post of its own; maybe in the future I could write about it.</p>

<h3 id="life-before-roslyn-bgen">Life Before Roslyn: bgen</h3>

<p>Before Roslyn existed, the approach that <a href="https://en.wikipedia.org/wiki/Miguel_de_Icaza">Miguel de Icaza</a> took was to 
write an application that would generate the bindings for us. In his impressive wisdom, Miguel realized that the best 
way to describe how an API from Objective-C should be exposed in C# was by writing the binding contract itself in C#. 
To do so, the first (and current) binding generator for Xamarin.iOS was born (bgen). To tackle this task without
access to the compiler is very ingenious; the bindings contract would be written as interfaces that the generator would 
load via reflection and generate the bindings based on that. This approach worked well for bgen, especially before the
introduction of nullable annotations in both C# and Objective-C. The process of generating the bindings would follow
this pipeline:</p>

<pre><code class="language-mermaid">graph TD
    A[Parse Obj-C Headers] --&gt;|LLVM| B[Generate Definitions]
    B --&gt;|C# Interfaces| C[Compile Intermediate DLL]
    C --&gt;|Reflection| D[Run bgen]
    D --&gt;|Write .g.cs Files| E[Final Compilation]
</code></pre>
<ol>
  <li><em>Parse Obj-C Headers:</em> The process begins by using LLVM to parse the original Objective-C header files to understand 
the native API structure.</li>
  <li><em>Generate Definitions:</em> Intermediate C# files are generated. These act as a “contract” using interfaces and
attributes to describe how the Objective-C API should be mapped to C#.</li>
  <li><em>Compile Intermediate DLL:</em> These interface definitions are compiled into a temporary assembly (DLL). This assembly
contains the metadata needed for the next step.</li>
  <li><em>Run bgen:</em> The generator tool (bgen) loads the intermediate DLL via reflection. It processes the attributes and
interface structures to produce the actual glue code (generated as .g.cs files).</li>
  <li><em>Final Compilation:</em> The generated source files are combined with any manual “sugar” code or hand-written wrappers
and compiled into the final library.</li>
</ol>

<p>While the above pipeline is basic, it is still a very powerful tool that can be used to generate bindings for 
Objective-C APIs. Nevertheless, the more we had to bind, the more complex the process became:</p>

<ol>
  <li>The process required a lot of manual work. The original bgen tool would generate all the bindings but would not update 
the current ones.</li>
  <li>Apple would make changes to the Objective-C APIs that would require us to update the bindings, yet we had to maintain backward 
compatibility, meaning we had to write manual code to handle the changes.</li>
  <li>The process was slow. It took a lot of time to generate the bindings—everybody knows that reflection is slow, and to 
that, we had to add the extra compilation steps.</li>
  <li>Although the pipeline is simple, when a new symbol had to be introduced into the project, it was complicated to
understand where in the pipeline it had to be added. Should it be added in the first compilation step or in the last one?</li>
  <li>Using reflection resulted in a lot of complicated code that would copy attributes from the C# contracts to the final
generated code. The most important part of the attributes were those used to describe the platforms supported by the API.</li>
  <li>Reflection has its limitations; for example, reflection does not see a difference between <code>Action&lt;string&gt;</code> and
<code>Action&lt;string?&gt;</code>.</li>
</ol>

<h3 id="moving-to-roslyn-from-api-to-language">Moving to Roslyn: From API to Language</h3>

<p>Moving to Roslyn allowed us to solve all the above problems and make the process much simpler. We moved from several
compilations to a single compilation. What is more, we could use Roslyn to generate the bindings for us as well as 
access the semantic model of Roslyn to make decisions based on the API structure.</p>

<p>Roslyn generators allowed us to create a more concise API via the use of generic attributes (yes, they exist) for the
contract definitions. For example:</p>

<pre><code class="language-csharp">[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("maccatalyst13.1")]
[BindingType&lt;Class&gt;]
public partial class MyObjCClass {

    [Export&lt;Constructor&gt; ("initWithScheme:host:path:")]
	public ConstructorTests (string scheme, string host, string path);

    [SupportedOSPlatform ("ios")]
	[SupportedOSPlatform ("tvos")]
	[SupportedOSPlatform ("macos")]
	[SupportedOSPlatform ("maccatalyst13.1")]
	[Export&lt;Property&gt; ("count")]
	public virtual partial nuint Count { get; set; }

    [SupportedOSPlatform ("ios")]
	[SupportedOSPlatform ("tvos")]
	[SupportedOSPlatform ("macos")]
	[SupportedOSPlatform ("maccatalyst13.1")]
	[Export&lt;Method&gt; ("valueForKey:", Flags = Method.MarshalNativeExceptions)]
	public virtual unsafe partial NSObject ValueForKey (NSString key);
	
	
}
</code></pre>

<p>If you look closely, the binding is using the <code>ExportAttribute</code> to describe the Objective-C API for constructors, properties, 
and methods. This is invaluable because it allows us to set the <code>Flags</code> property definition to match that of the enum passed
to the <code>ExportAttribute</code>. What does this mean? Well, it means that we can use a single attribute to describe both 
properties and methods; at the same time, the compiler will know which one it is and will ensure that the flags match.</p>

<p>I defined the export attribute as follows:</p>

<pre><code class="language-csharp">using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using Registrar;

#nullable enable

namespace ObjCBindings {

	[Experimental ("APL0003")]
	[AttributeUsage (AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]
	public class ExportAttribute&lt;T&gt; : Attribute where T : Enum {

		/// &lt;summary&gt;
		/// Get/Set the selector that is exposed by the decorated method.
		/// &lt;/summary &gt;
		public string? Selector { get; set; } = null;

		/// &lt;summary&gt;
		/// Get/Set the export configuration flags.
		/// &lt;/summary &gt;
		public T? Flags { get; set; } = default (T);

		/// &lt;summary&gt;
		/// Get/Set the argument semantics to be used with the native method.
		///  &lt;/summary &gt;
		public ArgumentSemantic ArgumentSemantic { get; set; } = ArgumentSemantic.None;

		/// &lt;summary&gt;
		/// Get/Set the native prefix to be used in the custom marshal directive.
		///
		/// The generator will only respect this value if the CustomMarshalDirective flag is set.
		/// If the flag is not set the analyzer will raise a compiling error.
		/// &lt;/summary &gt;
		public string? NativePrefix { get; set; } = null;

		/// &lt;summary&gt;
		/// Get/Set the native suffix to be used in the custom marshal directive.
		///
		/// The generator will only respect this value if the CustomMarshalDirective flag is set.
		/// If the flag is not set the analyzer will raise a compiling error.
		/// &lt;/summary &gt;
		public string? NativeSuffix { get; set; } = null;

		/// &lt;summary&gt;
		/// Get/Set the library to be used in the custom marshal directive.
		///
		/// The generator will only respect this value if the CustomMarshalDirective flag is set.
		/// If the flag is not set the analyzer will raise a compiling error.
		/// &lt;/summary &gt;
		public string? Library { get; set; } = null;

		/// &lt;summary&gt;
		/// The type of the result for an async method.
		/// &lt;/summary&gt;
		public Type? ResultType { get; set; } = null;

		/// &lt;summary&gt;
		/// The name of the generated async method.
		/// &lt;/summary&gt;
		public string? MethodName { get; set; } = null;

		/// &lt;summary&gt;
		/// The name of the type of the result for an async method.
		/// &lt;/summary&gt;
		public string? ResultTypeName { get; set; } = null;

		/// &lt;summary&gt;
		/// A code snippet to be executed after the async method call.
		/// &lt;/summary&gt;
		public string? PostNonResultSnippet { get; set; } = null;

		/// &lt;summary&gt;
		/// The type of the strong delegate for a weak delegate property.
		/// &lt;/summary&gt;
		public Type? StrongDelegateType { get; set; } = null;

		/// &lt;summary&gt;
		/// The name of the strong delegate for a weak delegate property.
		/// &lt;/summary&gt;
		public string? StrongDelegateName { get; set; } = null;

		/// &lt;summary&gt;
		/// Get/Set the type of the strong dictionary key class.
		/// &lt;/summary&gt;
		public Type? StrongDictionaryKeyClass { get; set; } = null;

		/// &lt;summary&gt;
		/// The type of the event args to use.
		/// &lt;/summary&gt;
		public Type? EventArgsType { get; set; } = null;

		/// &lt;summary&gt;
		/// The name of the type of the event args method.
		/// &lt;/summary&gt;
		public string? EventArgsTypeName { get; set; } = null;

		protected ExportAttribute () { }

		/// &lt;summary&gt;
		/// Mark a managed method as an exported selector.
		/// &lt;/summary &gt;
		/// &lt;param name="selector"&gt;The native selector to be exported.&lt;/param&gt;
		public ExportAttribute (string? selector)
		{
			Selector = selector;
		}

		/// &lt;summary&gt;
		/// Mark a managed method as an exported selector.
		/// &lt;/summary &gt;
		/// &lt;param name="selector"&gt;The native selector to be exported.&lt;/param&gt;
		/// &lt;param name="semantic"&gt;The argument semantics to use when calling the native selector.&lt;/param&gt;
		public ExportAttribute (string? selector, ArgumentSemantic semantic)
		{
			Selector = selector;
			ArgumentSemantic = semantic;
			Flags = default (T);
		}

		/// &lt;summary&gt;
		/// Mark a managed method as an exported selector.
		/// &lt;/summary &gt;
		/// &lt;param name="selector"&gt;The native selector to be exported.&lt;/param&gt;
		/// &lt;param name="flags"&gt;The configuration flags for the managed method.&lt;/param&gt;
		public ExportAttribute (string? selector, T? flags)
		{
			Selector = selector;
			ArgumentSemantic = ArgumentSemantic.None;
			Flags = flags;
		}

		/// &lt;summary&gt;
		/// Mark a managed method as an exported selector.
		/// &lt;/summary &gt;
		/// &lt;param name="selector"&gt;The native selector to be exported.&lt;/param&gt;
		/// &lt;param name="semantic"&gt;The argument semantics to use when calling the native selector.&lt;/param&gt;
		/// &lt;param name="flags"&gt;The configuration flags for the managed method.&lt;/param&gt;
		public ExportAttribute (string? selector, ArgumentSemantic semantic, T? flags)
		{
			Selector = selector;
			ArgumentSemantic = semantic;
			Flags = flags;
		}
	}
}
</code></pre>

<p>Any avid reader will notice that the <code>ExportAttribute</code> is generic and, not only that, but it exposes properties that do not
make sense in all possible cases. For example, <code>StrongDelegateType</code> only makes sense for weak delegate properties, but
not on methods. This is an example where C# as a general-purpose language is not enough to describe the Objective-C APIs.
The language allows us to describe something that the semantics of the API does not allow; we will talk about this in 
the Roslyn analyzers section. That is when we move from a simple API to describe bindings to a DSL that has both
syntax and semantics. For anyone interested, all the attributes are defined in the <a href="https://github.com/dotnet/macios/tree/main/src/ObjCBindings">ObjCBindings</a> namespace.</p>

<p><a href="https://github.com/dotnet/macios/tree/main/src/rgen/Microsoft.Macios.Generator">Rgen</a>, in the Microsoft.iOS project,
contains the generator for the bindings. I do want to point out the architecture of the generator:</p>

<ol>
  <li>The generator contains a data model type. The reason for that is to ensure that the incremental generator is able 
to be efficient and generate code only when needed. The other use of the data model was to share it between the 
analyzers, the LLVM parser, and the generator.</li>
  <li>I am one of the few people that use the Roslyn API to generate code. A lot of developers use a <code>StringBuilder</code> with 
interpolation to generate code, but that is an error in large code generators. My approach was to create a <code>SyntaxFactory</code> 
similar to the one provided by Roslyn that would use tokens. This approach allows writing code such as 
<code>AssignVariable (GetReturnVariableName (), ConvertToManaged (method, invocation)!);</code>. This allows doing function 
composition to generate statements. Some devs might notice that I took a very functional programming approach with
readonly structures and function composition.</li>
  <li>The generator main code uses pattern matching to generate the code. The generator will get the API contract and,
based on the contract definition, use an emitter for the specific contract to generate. That way, unit tests are 
straightforward to write and the generator code is very concise.</li>
</ol>

<p>At this point, we have our first step in the process: we can create a contract definition and compile it, but we still
need to ensure that the binding definitions are semantically correct.</p>

<h2 id="roslyn-analyzers---from-valid-c-to-valid-binding">Roslyn analyzers - From “Valid C#” to “Valid Binding”</h2>

<p>The analyzer is probably the cornerstone to ensure that we move from a simple API to a DSL that has both syntax and 
semantics. The analyzer will be able to detect errors in the binding contract and report them to the user at compile 
time. This is a crucial step because it will ensure that the bindings are correct and that we do not break the 
API contract as well as the semantics of the Objective-C APIs. Let’s look at an example:</p>

<pre><code class="language-csharp">using System;
using System.Runtime.Versioning;
using AVFoundation;
using CoreGraphics;
using Foundation;
using ObjCBindings;
using ObjCRuntime;
using nfloat = System.Runtime.InteropServices.NFloat;

namespace TestNamespace;

[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("maccatalyst13.1")]
[BindingType&lt;Class&gt;]
public partial class TestClass{

	[SupportedOSPlatform ("ios")]
	[SupportedOSPlatform ("tvos")]
	[SupportedOSPlatform ("macos")]
	[SupportedOSPlatform ("maccatalyst13.1")]
	[Export&lt;Property&gt; ("delegate",
		ArgumentSemantic.Weak,
		Flags = Property.WeakDelegate,
		StrongDelegateType = typeof (INSUserActivityDelegate),
		StrongDelegateName = "Delegate"
	)]
	public virtual partial NSObject? OtherWeakDelegate { get; set; }

	[SupportedOSPlatform ("ios")]
	[SupportedOSPlatform ("tvos")]
	[SupportedOSPlatform ("macos")]
	[SupportedOSPlatform ("maccatalyst13.1")]
	[Export&lt;Property&gt; ("delegateSecond",
		ArgumentSemantic.Weak,
		Flags = Property.WeakDelegate,
		StrongDelegateType = typeof (INSUserActivityDelegate),
		StrongDelegateName = "Delegate"
	)]
	public virtual partial NSObject? WeakSecondDelegate { get; set; }
}
</code></pre>

<p>The above code is valid C#; there is nothing that the compiler would complain about, yet it is an invalid Objective-C 
binding. The issue with the above code is that the weak delegate ‘WeakSecondDelegate’ strong delegate ‘Delegate’ 
is already used by ‘OtherWeakDelegate’. I cannot stress enough how important this is: we are moving from an API that 
allows us to define the bindings to a DSL in which the binding definitions are semantically correct and the syntax is 
straightforward.</p>

<p>Currently, with the bgen implementation used by the project, the only time errors could be reported was at generation
time, and dealing with the errors was a pain. With Roslyn analyzers, we can report errors at compile time and provide 
the user with a detailed error message as well as a fix suggestion. Not only that, but diagnostic messages 
are clickable on any modern editor and will take the user directly to the line of code that caused the error.</p>

<p>If you look at the Microsoft.iOS project, you will notice that the most complicated code is in the generator, yet the
code that provides the most value is in the analyzers.</p>

<h2 id="roslyn-code-fix-providers">Roslyn code fix providers</h2>

<p>The Roslyn code fix providers allow us to complete the full developer experience. If the analyzer reports an error, the
user can click on the error and the code fix provider will be able to provide a fix suggestion. Because we can provide
semantic validations, we also can provide a fix suggestion that will automatically fix the error and correct any
issues that the user might have.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So far I have introduced the main idea of using Roslyn to generate bindings for Objective-C APIs and move from a simple 
API to a DSL that has both syntax and semantics. In the following posts, I will get into the implementation details of 
the Roslyn generators and analyzers, and how I managed to write a huge amount of unit tests for the generator. I am
sure there are a number of things that will help you understand the Roslyn APIs better as well as writing SDKs that 
use Roslyn to enforce semantic checks at compile time. I will also introduce the full API I was designing for 
the Microsoft.iOS project as well as how the rgen process was going to be exposed to an MCP to get Copilot to automatically
generate the bindings for us from the Objective-C headers.</p>

<p>This post sets the foundation. In the next parts, I’ll go deeper into:</p>

<ul>
  <li><a href="https://www.themacaque.com/2026/01/27/objc-vs-csharp.html">Part 2: Objective-C vs C#: A Conceptual Primer for Bindings</a></li>
  <li>Part 3: Generator architecture and incremental design</li>
  <li>Part 4: Writing analyzers that enforce semantic rules</li>
  <li>Part 5: Code fix providers and developer experience</li>
  <li>Part 6: Testing large Roslyn generators at scale</li>
  <li>Part 7: Exposing the binding pipeline to tools like Copilot</li>
</ul>]]></content><author><name>Manuel de la Peña Saenz</name></author><category term="Other" /><summary type="html"><![CDATA[I left Microsoft three months ago. During that time, I realized that very few people—both inside and outside the company—understood what the long-term goals of the Microsoft.iOS and Microsoft.MacOS projects actually were.]]></summary></entry><entry><title type="html">Detecting a nullable Type via reflection</title><link href="https://www.themacaque.com/2023/01/31/nullability-reflection-csharp.html" rel="alternate" type="text/html" title="Detecting a nullable Type via reflection" /><published>2023-01-31T00:00:00+00:00</published><updated>2023-01-31T00:00:00+00:00</updated><id>https://www.themacaque.com/2023/01/31/nullability-reflection-csharp</id><content type="html" xml:base="https://www.themacaque.com/2023/01/31/nullability-reflection-csharp.html"><![CDATA[<p>Lately we have been enabling, <a href="https://github.com/xamarin/xamarin-macios/pull/15163">step</a> by <a href="https://github.com/xamarin/xamarin-macios/pull/15162">step</a>, nullability on xamarin-macios. 
The idea being that the nullability annotations on our API will help Xamarin iOS and macOS users reduce their runtime issues due to a unexpected NRE (NullReferenceException). 
But xamarin-macios is special, most of the API is generated from interfaces, which makes the process more complicated.</p>

<hr />

<h2 id="how-is-xamarinios-and-xamarinmac-built">How is Xamarin.iOS and Xamarin.Mac built</h2>

<p>Generating bindings for an API as big as the one provided by Apple in their platforms is a incredibly hard thing to do, on top of already an impossible task,
one of the main goals of Xamarin back in the day was to provide next day support. As you can imaging, tackling such a task manually is
nearly impossible without a huge amount of developers. Thankfully, from very early in the project, the bindings were mostly generated.
I cannot say who were the original inceptors of the design but I have a very strong feeling (and some git logs) that the original idea was
from <a href="https://twitter.com/migueldeicaza">Miguel</a>. Understanding how Xamarin on iOS is build is important to understand why it has taking us 
so long to enable nullability over all the API.</p>

<p>Building Xamarin iOS is based on a very smart design. Rather than manually writing all the <a href="https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke">PInvokes</a> 
manually, the project uses a what I like to call a two pass compilation. The process is as follows:</p>

<ol>
  <li>Compile an intermediate assembly that contains the API definitions to be bound. This APIs are a contract (using interfaces) of the code that needs to be
generated. The idea is that this intermediate assemblies contain all the required information to later use reflection to infer the APIs to bind. Because
not all can be guessed from the <a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection">Reflection types</a> from C#, the
API definitions use a number of attribute to add metadata. The metadata goes from what platforms support a given API, to if an async method can be provided.</li>
  <li>There is a command line application that we call the generator that loads the intermediate assembly via reflection. The generator uses reflection to get all the
data it needs and writes all the glue code needed to create a full API.</li>
  <li>The last step of the build is to gather all those generated files (the use the extension *.g.cs) and combine them with some manual code (Xamarin is not a 1:1 map
of Apples APIs we add some extra APIs or some csharp sugar) and create the last file.</li>
</ol>

<p>The following code is an example of one of those API definitions:</p>

<pre><code class="language-csharp">[NoWatch, NoTV, iOS (10, 0)]
[MacCatalyst (13, 1)]
[DisableDefaultCtor]
[BaseType (typeof (UIFeedbackGenerator))]
interface UIImpactFeedbackGenerator {

    [Export ("initWithStyle:")]
     NativeHandle Constructor (UIImpactFeedbackStyle style);

    [Export ("impactOccurred")]
    void ImpactOccurred ();

    [iOS (13, 0)]
    [MacCatalyst (13, 1)]
    [Export ("impactOccurredWithIntensity:")]
    void ImpactOccurred (nfloat intensity);
}
</code></pre>

<p>And the corresponding generated code:</p>

<pre><code class="language-csharp">[Register("UIImpactFeedbackGenerator", true)]
[Unavailable (PlatformName.WatchOS, ObjCRuntime.PlatformArchitecture.All)]
[Unavailable (PlatformName.TvOS, ObjCRuntime.PlatformArchitecture.All)]
[Introduced (PlatformName.iOS, 10,0, ObjCRuntime.PlatformArchitecture.All)]
public unsafe partial class UIImpactFeedbackGenerator : UIFeedbackGenerator {
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        static readonly IntPtr class_ptr = Class.GetHandle ("UIImpactFeedbackGenerator");
        public override IntPtr ClassHandle { get { return class_ptr; } }
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected UIImpactFeedbackGenerator (NSObjectFlag t) : base (t)
        {
        }

        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected internal UIImpactFeedbackGenerator (IntPtr handle) : base (handle)
        {
        }

        [Export ("initWithStyle:")]
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        public UIImpactFeedbackGenerator (UIImpactFeedbackStyle style)
                : base (NSObjectFlag.Empty)
        {
                global::UIKit.UIApplication.EnsureUIThread ();
                if (IsDirectBinding) {
                        InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr (this.Handle, Selector.GetHandle ("initWithStyle:"), (IntPtr) (long) style), "initWithStyle:");
                } else {
                        InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_IntPtr (this.SuperHandle, Selector.GetHandle ("initWithStyle:"), (IntPtr) (l
ong) style), "initWithStyle:");
                }
        }
        [Export ("impactOccurred")]
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        public virtual void ImpactOccurred ()
        {
                global::UIKit.UIApplication.EnsureUIThread ();
                if (IsDirectBinding) {
                        global::ObjCRuntime.Messaging.void_objc_msgSend (this.Handle, Selector.GetHandle ("impactOccurred"));
                } else {
                        global::ObjCRuntime.Messaging.void_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("impactOccurred"));
                }
        }
        [Export ("impactOccurredWithIntensity:")]
        [Introduced (PlatformName.iOS, 13,0, ObjCRuntime.PlatformArchitecture.All)]
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        public virtual void ImpactOccurred (nfloat intensity)
        {
        #if ARCH_32
                throw new PlatformNotSupportedException ("This API is not supported on this version of iOS");
        #else
                global::UIKit.UIApplication.EnsureUIThread ();
                if (IsDirectBinding) {
                        global::ObjCRuntime.Messaging.void_objc_msgSend_nfloat (this.Handle, Selector.GetHandle ("impactOccurredWithIntensity:"), intensity);
                } else {
                        global::ObjCRuntime.Messaging.void_objc_msgSendSuper_nfloat (this.SuperHandle, Selector.GetHandle ("impactOccurredWithIntensity:"), intensity);
                }
        #endif
        }
} /* class UIImpactFeedbackGenerator */
</code></pre>

<p><strong>PS:</strong> In this post I am ignoring the amazing work that <a href="https://twitter.com/rolfkvinge">Rolf</a> does in teh runtime and the native code needed to write the trampolines
between C# and ObjC, but that is probably a good topic for another post.
<strong>PS PS</strong>: All this work was done A LONG time before code generators were a thing, and I would argue they are not powerful enough for our use case.</p>

<h2 id="nullability-before-dotnet-had-nullability">Nullability before dotnet had nullability</h2>

<p>Full nullability support to dotnet (that includes references types) was added in C# 7 yet objectiveC had Nullability annotations earlier than that. Perse that was not an issue
for Xamarin since we could annotate the not null parameters in our API definitions and generate the right code, for example:</p>

<pre><code class="language-csharp">[NoWatch, NoTV, iOS (15, 0), MacCatalyst (15, 0)]
[BaseType (typeof (UIPresentationController))]
[DisableDefaultCtor]
interface UISheetPresentationController {

    [Export ("initWithPresentedViewController:presentingViewController:")]
    [DesignatedInitializer]
    NativeHandle Constructor (UIViewController presentedViewController, [NullAllowed] UIViewController presentingViewController);

    // rest of the API

}
</code></pre>

<p>That would generated the following:</p>
<pre><code class="language-csharp">[Export ("initWithPresentedViewController:presentingViewController:")]
[DesignatedInitializer]
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
public UISheetPresentationController (UIViewController presentedViewController, UIViewController? presentingViewController)
        : base (NSObjectFlag.Empty)
{
#if ARCH_32
        throw new PlatformNotSupportedException ("This API is not supported on this version of iOS");
#else
        global::UIKit.UIApplication.EnsureUIThread ();
        var presentedViewController__handle__ = presentedViewController!.GetNonNullHandle (nameof (presentedViewController));
        var presentingViewController__handle__ = presentingViewController.GetHandle ();
        if (IsDirectBinding) {
                InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr_IntPtr (this.Handle, Selector.GetHandle ("initWithPresentedViewController:
presentingViewController:"), presentedViewController__handle__, presentingViewController__handle__), "initWithPresentedViewController:presentingViewController:");
        } else {
                InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_IntPtr_IntPtr (this.SuperHandle, Selector.GetHandle ("initWithPresentedViewC
ontroller:presentingViewController:"), presentedViewController__handle__, presentingViewController__handle__), "initWithPresentedViewController:presentingViewController:");
        }
#endif
}
</code></pre>

<p>This approached worked very very well for those parts of the API we control, and it was great when nullability for reference types did not exist. Yet, the approach before
nullability had a few annoying corner cases. There is no way to add attributes to the following:</p>

<h3 id="generic-type-definitions">Generic type definitions</h3>

<p>You cannot do:</p>
<pre><code class="language-csharp">public Dictionary&lt;string, [NotNull] MyObject&gt; Data { get; set;}
</code></pre>

<h3 id="delegates">Delegates</h3>

<p>This is perse a limitation of the generator, but the following does not work:</p>

<pre><code class="language-csharp">delegate NSComparisonResult NSComparator ([NotNull] NSObject obj1, [NotNull] NSObject obj2);
</code></pre>

<p>Once nullability was added to C# 7 we wanted to add annotations to all the code, and while we supported a number of uses cases, using attributes would not fix
the situation in which we had to annotate generic types.</p>

<h2 id="moving-forward">Moving forward</h2>

<p>dotnet 6 brought with it <a href="https://learn.microsoft.com/en-us/dotnet/api/system.reflection.nullabilityinfocontext?view=net-7.0">NullabilityInfoContext</a> the API we needed to
be able to use ? as a way to annotate nullability in our API definitions, the problem, Xamarin iOS had to maintain support
to code older than dotnet 6. This supposed a problem, we could nto use the new APIs, yet it gave me an idea. Rather that doing some complicated use of pramgas or two different
versions of the generator, I decided to do my own implementation of the NullabilityInfoContext for pre dotnet 6 code. That was added and landed in this <a href="https://github.com/xamarin/xamarin-macios/pull/17384">PR</a>.</p>

<p>The code follows the <a href="https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md">documentation from the dotnet team</a> to read the nullability metadata. In reality,
the process is very simple.</p>

<ol>
  <li>If we are looking at a value type. Check if the compiler converted int? into Nullable<int></int></li>
  <li>If we are looking at a reference type read the CustomAttributeData from the declaring type and decide accordingly.</li>
</ol>

<p>The two attributes we want to look at are:</p>

<ul>
  <li>System.Runtime.CompilerServices.NullableAttribute</li>
  <li>System.Runtime.CompilerServices.NullableContextAttribute</li>
</ul>

<p>Both of the use bytes to let you know what nullability state the type has, being:</p>

<ul>
  <li>0: Unknown</li>
  <li>1: Not nullable</li>
  <li>2: Nullable</li>
</ul>

<p>Teh implementation I wrote is very simple and uses recursion, could have been implemented as a stack, but I prefer clarity for this code which should die soon. Ideally, this implementation
will only be used by the generator during the time we have to compile it pre dotnet6 (we already have a dotnet 6 version). Here is the very simple implementation:</p>

<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;

#nullable enable

namespace bgen {

	// extension shared between NET and !NET make our lives a little easier
	public static class NullabilityInfoExtensions {
		public static bool IsNullable (this NullabilityInfo self)
			=&gt; self.ReadState == NullabilityState.Nullable;
	}
#if !NET

	// from: https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md
	public enum NullabilityState : byte {
		Unknown = 0,
		NotNull = 1,
		Nullable = 2,
	}

	public class NullabilityInfo {
		public NullabilityState ReadState { get; set; } = NullabilityState.NotNull;
		public Type? Type { get; set; }
		public NullabilityInfo? ElementType { get; set; }
		public NullabilityInfo []? GenericTypeArguments { get; set; }
	}

	// helper class that allows to check the custom attrs in a method,property or field
	// in order for the generator to support ? in the bindings definition. This class should be
	// replaced by NullabilityInfoContext from the dotnet6 in the future. The API can be maintained (hopefully).
	public class NullabilityInfoContext {

		static readonly string NullableAttributeName = "System.Runtime.CompilerServices.NullableAttribute";
		static readonly string NullableContextAttributeName = "System.Runtime.CompilerServices.NullableContextAttribute";

		public NullabilityInfoContext () { }

		public NullabilityInfo Create (PropertyInfo propertyInfo)
			=&gt; Create (propertyInfo.PropertyType, propertyInfo.DeclaringType, propertyInfo.CustomAttributes);

		public NullabilityInfo Create (FieldInfo fieldInfo)
			=&gt; Create (fieldInfo.FieldType, fieldInfo.DeclaringType, fieldInfo.CustomAttributes);

		public NullabilityInfo Create (ParameterInfo parameterInfo)
			=&gt; Create (parameterInfo.ParameterType, parameterInfo.Member, parameterInfo.CustomAttributes);

		static NullabilityInfo Create (Type memberType, MemberInfo? declaringType,
			IEnumerable&lt;CustomAttributeData&gt;? customAttributes, int depth = 0)
		{
			var info = new NullabilityInfo { Type = memberType };

			if (memberType.IsByRef) {
				var e = memberType.GetElementType ();
				info = Create (e, declaringType, customAttributes);
				// override the returned type because it is returning e and not ref e
				info.Type = memberType;
				return info;
			}

			if (memberType.IsArray) {
				info.ElementType = Create (memberType.GetElementType ()!, declaringType, customAttributes, depth + 1);
			}

			if (memberType.IsGenericType) {
				// we need to get the nullability type of each of the generics, we use an array to make sure
				// order is kept
				var genericArguments = memberType.GetGenericArguments ();
				info.GenericTypeArguments = new NullabilityInfo [genericArguments.Length];
				for (int i = 0; i &lt; genericArguments.Length; i++) {
					info.GenericTypeArguments [i] = Create (genericArguments [i], declaringType,
						customAttributes, depth + (i + 1)); // the depth can be complicated
				}
			}

			// there are two possible cases we have to take care of:
			//
			// 1. ValueType: If we are dealing with a value type the compiler converts it to Nullable&lt;ValueType&gt;
			// 2. ReferenceType: Ret types do not use an interface, but a custom attr is used in the signature

			if (memberType.IsValueType) {
				var nullableType = Nullable.GetUnderlyingType (memberType);
				info.ReadState = nullableType != null ? NullabilityState.Nullable : NullabilityState.NotNull;
				info.Type = memberType;
				return info;
			}

			// at this point, we have to use the attributes (metadata) added by the compiler to decide if we have a
			// nullable type or not from a ref type. From https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md
			//
			// The byte[] is constructed as follows:
			// Reference type: the nullability (0, 1, or 2), followed by the representation of the type arguments in order including containing types
			// Nullable value type: the representation of the type argument only
			// Non-generic value type: skipped
			// 	Generic value type: 0, followed by the representation of the type arguments in order including containing types
			// Array: the nullability (0, 1, or 2), followed by the representation of the element type
			// Tuple: the representation of the underlying constructed type
			// 	Type parameter reference: the nullability (0, 1, or 2, with 0 for unconstrained type parameter)

			// interesting case when we have constrains from a generic method
			if ((customAttributes?.Count () ?? 0) == 0 &amp;&amp; memberType.CustomAttributes is not null)
				customAttributes = memberType.CustomAttributes;

			var nullable = customAttributes?.FirstOrDefault (
				x =&gt; x.AttributeType.FullName == NullableAttributeName);

			var flag = NullabilityState.Unknown;
			if (nullable is not null &amp;&amp; nullable.ConstructorArguments.Count == 1) {
				var attributeArgument = nullable.ConstructorArguments [0];
				if (attributeArgument.ArgumentType == typeof (byte [])) {
					var args = (ReadOnlyCollection&lt;CustomAttributeTypedArgument&gt;) attributeArgument.Value!;
					if (args.Count &gt; 0 &amp;&amp; args [depth].ArgumentType == typeof (byte)) {
						flag = (NullabilityState) args [depth].Value;

					}
				} else if (attributeArgument.ArgumentType == typeof (byte)) {
					flag = (NullabilityState) attributeArgument.Value!;
				}

				info.Type = memberType;
				info.ReadState = flag;
				return info;
			}

			// we are using the context of the declaring type to decide if the type is nullable
			for (var type = declaringType; type is not null; type = type.DeclaringType) {
				var context = type.CustomAttributes
					.FirstOrDefault (x =&gt; x.AttributeType.FullName == NullableContextAttributeName);
				if (context is null ||
					context.ConstructorArguments.Count != 1 ||
					context.ConstructorArguments [0].ArgumentType != typeof (byte))
					continue;
				if (NullabilityState.Nullable == (NullabilityState) context.ConstructorArguments [0].Value!) {
					info.Type = memberType;
					info.ReadState = NullabilityState.Nullable;
					return info;
				}
			}

			// we need to consider the generic constrains
			if (!memberType.IsGenericParameter)
				return info;

			// if we do have a custom null atr in any of them, use it
			if (memberType.GenericParameterAttributes.HasFlag (GenericParameterAttributes.NotNullableValueTypeConstraint))
				info.ReadState = NullabilityState.NotNull;

			return info;
		}
	}
#endif

}
</code></pre>

<p>I have not complited all the work, but soon I should have added the needed support for the generator which will fix a number <a href="https://github.com/xamarin/xamarin-macios/issues/4826">of</a>
<a href="https://github.com/xamarin/xamarin-macios/issues/9814">API</a> <a href="https://github.com/xamarin/xamarin-macios/issues/17109">bugs</a>.</p>

<p>This post has to goals:</p>

<ol>
  <li>Let you know we are working on it and you will get nullability soon.</li>
  <li>Provide the sample code needed to understand what is the c# compiler doing when you use ? in you value types and what it does in your reference types.</li>
</ol>

<p>I hope you find it as interesting as I do. If you already knew about this, sorry for making you read.</p>]]></content><author><name>Manuel de la Peña Saenz</name></author><category term="Other" /><summary type="html"><![CDATA[Lately we have been enabling, step by step, nullability on xamarin-macios. The idea being that the nullability annotations on our API will help Xamarin iOS and macOS users reduce their runtime issues due to a unexpected NRE (NullReferenceException). But xamarin-macios is special, most of the API is generated from interfaces, which makes the process more complicated.]]></summary></entry><entry><title type="html">Using GraphQL to perform GitHub searches with C#</title><link href="https://www.themacaque.com/2022/12/20/graphql-and-csharp.html" rel="alternate" type="text/html" title="Using GraphQL to perform GitHub searches with C#" /><published>2022-12-20T00:00:00+00:00</published><updated>2022-12-20T00:00:00+00:00</updated><id>https://www.themacaque.com/2022/12/20/graphql-and-csharp</id><content type="html" xml:base="https://www.themacaque.com/2022/12/20/graphql-and-csharp.html"><![CDATA[<p>I am not the most avid blogger, and I do not plan to be one. However I have decided to write some blogs/notes about programming problems I have solved to which there is not much,
if any, documentation online. This post is going to focus on a small problem I found recently when trying to use <a href="https://github.com/octokit/octokit.net">octokit.net</a> to find a collection of pull requests and their reviews.</p>

<hr />

<h2 id="the-problem">The problem</h2>

<p>We want to be able to retrieve all the pull requests created in a repository between two given dates and all their reviews.</p>

<h2 id="the-naive-approach-using-the-rest-api">The naive approach: using the REST API</h2>

<p>If one wants to interact with the Github API the first thing it does is to search in the <a href="http://octokitnet.readthedocs.io/en/latest/.">Octokit documentation</a> to find a solution for their needs</p>

<p>The documentation has a number of examples, but to make it easier for the reader I have added the needed code to get you started. Perse the implementation is quite simple and very naive. Retrieving the PRs can be divided in 4 different operations:</p>

<ol>
  <li>Create a github client to interact with the GitHub API.</li>
  <li>Perform a search with the criteria we have using <a href="https://octokitnet.readthedocs.io/en/latest/search/#search-pull-requests">GitHubs search API</a>.</li>
  <li>Get all the PRs returned by the search.</li>
  <li>Retrieve all the PR reviews.</li>
</ol>

<p>There is no more to it than that, lets take a look at the actual implementation:</p>

<h3 id="implementation-using-octokit">Implementation using Octokit</h3>

<p>The following snippets are from an actual application I have been writing. Where ever you see a Log.Error/Debug just think about it as a log message (I am using <a href="https://github.com/serilog/serilog-sinks-console">Serilog.Sinks.Console</a>).</p>

<p>Perform the search.</p>

<pre><code class="language-csharp">async Task&lt;SearchIssuesResult?&gt; GetPullRequests (string repository, DateTimeOffset startDate, DateTimeOffset endDate)
{
    // we need to use the search api for this:
    try {
        var request = new SearchIssuesRequest {
            Type = IssueTypeQualifier.PullRequest,
            Repos = new () { repository },
            Created = new (startDate, endDate),
        };
        return await ghClient.Search.SearchIssues (request).ConfigureAwait (false); // yes, PRs are issues.. \0/ 
    } catch (Exception e) {
        Log.Debug ("Exception {@Message} searching for pull requests", e.Message);
        return default;
    }
}
</code></pre>

<p>Once we have received the search results, we need to get the actual PR objects. The search API does not return a PR but a
<a href="https://github.com/octokit/octokit.net/blob/356588288e2d07fb4844911f6e03ef129540b124/Octokit/Models/Response/SearchIssuesResult.cs">SearchIssueResult</a> which is an object that
represents both, pull requests and issues. As the comment in the code says, GitHub treats PRs as issues. The SearchIssuesResult contains all the numbers of the PRs that
match the search. Ideally at this point, we would be done. We could get all the numbers of the PRs that matched and retrieve them, but there are two important details
that we need to tackle:</p>

<ol>
  <li>There is no API in Octokit to get a collection of PRs, you can only retrieve them one by one <a href="https://github.com/octokit/octokit.net/blob/b312ae6a050fdef3b5093afe6088782b002c1cd0/Octokit/Clients/PullRequestsClient.cs">source</a> and the <a href="https://github.com/octokit/octokit.net/blob/b312ae6a050fdef3b5093afe6088782b002c1cd0/Octokit/Models/Request/PullRequestRequest.cs">criteria object</a> is not very helpful.</li>
  <li>The PullRequest object does not have full PullRequestReview objects, it only have a reference to the requested users <a href="https://github.com/octokit/octokit.net/blob/b312ae6a050fdef3b5093afe6088782b002c1cd0/Octokit/Models/Response/PullRequest.cs">source</a>.</li>
</ol>

<p>The above makes our code a little uglier, yet doable. We can create a single method that will take a PR number, get the PR object AND all of its reviews and returns those in a tuple. Once
we have the method for a single PR, we can create a method that will do the loop in an async way so that all requests are done in parallel.</p>

<pre><code class="language-csharp">async Task&lt;(PullRequest Request, PullRequestReview [] Reviews)?&gt; GetPullRequest (RepositoryConfig repositoryConfig, int pullRequestId)
{
    try {
        var pullRequest = await ghClient.PullRequest
            .Get (repositoryConfig.Org, repositoryConfig.Project, pullRequestId)
            .ConfigureAwait (false);
        Log.Debug ("SUCCESS retrieving PR {@PRId} in repo {@Org}/{@Project}", pullRequestId, repositoryConfig.Org, repositoryConfig.Project);
        // retrieve the reviews for the PR
        var reviews = await ghClient.PullRequest.Review
            .GetAll (repositoryConfig.Org, repositoryConfig.Project, pullRequestId).ConfigureAwait (false);
        var reviewsArray = reviews.ToArray ();
        Log.Debug ("SUCCESS retrieved {@ReviewCount} reviews for PR {@PRId} in {@Org}/{@Prject}", reviewsArray.Length, pullRequestId, repositoryConfig.Org, repositoryConfig.Project);
        return (Request: pullRequest, Reviews: reviewsArray);
    } catch (Exception e) {
        Log.Error ("Exception {@Message} when trying to retrieve PR {@PRId} in repo {@Org}/{@Project}", e.Message, pullRequestId, repositoryConfig.Org, repositoryConfig.Project);
        return default;
    }
}

Task&lt;(PullRequest Request, PullRequestReview [] Reviewa)? []&gt; GetAllPullRequests (RepositoryConfig repository, IReadOnlyList&lt;int&gt; ids)
{
    var prTasks = new Task&lt;(PullRequest Request, PullRequestReview [] Reviews)?&gt; [ids.Count];
    for (int index = 0; index &lt; prTasks.Length; index++) {
        prTasks [index] = GetPullRequest (repository, ids [index]);
    }
    return Task.WhenAll (prTasks);
}
</code></pre>

<p>If you look at the above code and wonder why I an using a Task.WhenAll rather than an await per loop instruction, ping me, we need to talk :)</p>

<p>Later in your code you can use the methods as follows:</p>

<pre><code class="language-csharp">DateTimeOffset startDateTimeOffset = new (startDate.ToDateTime (TimeOnly.MinValue));
DateTimeOffset endDateTimeOffset = new (endDate.ToDateTime (TimeOnly.MaxValue));

var prsSearchTask = GetPullRequests ($"{repository.Org}/{repository.Project}", startDateTimeOffset, endDateTimeOffset);

var searchResult = await prsSearchTask.ConfigureAwait (false);

var prsTask = GetAllPullRequests (repository, prsSearchTask.Result.Items.Select (i =&gt; i.Number).ToArray ());
</code></pre>

<h3 id="you-have-exceeded-a-secondary-rate-limit">You have exceeded a secondary rate limit…</h3>

<p>The above code works perfectly alright, but has a fundamental flaw, it uses <em>a lot</em> of requests. And because we are performing the requests in parallel
you will get a secondary threshold warning from GitHub:</p>

<blockquote>
  <p>HTTP/2 403
Content-Type: application/json; charset=utf-8
Connection: close</p>
</blockquote>

<blockquote>
  <p>{
  “message”: “You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later.”,
  “documentation_url”: “https://docs.github.com/rest/overview/resources-in-the-rest-api#secondary-rate-limits”
}</p>
</blockquote>

<p>At this point, we need to think about how we are approaching the problem. There are several things we can do to fix the rate problem:</p>

<ol>
  <li>Throttle our application. Request the limits of our PAT and adapt to it.</li>
  <li>Reduce the number of requests.</li>
</ol>

<p>I am a fan of implementing both solutions. Not because we reduce the number of requests we are going to fix the problem, it will simply take us longer to get
there. Writing code that throttles is not a hard problem to solve, and I am sure there are lots of solutions out there, but how do we reduce the number of
requests if we do not own the GitHub REST API? The best approach is to move away from the REST API and uses <a href="https://docs.github.com/en/graphql">GitHub GraphQL endpoint</a>.</p>

<h2 id="solving-the-problem-with-graphql">Solving the problem with GraphQL</h2>

<p>We already have seen how to solve the problem with Octokit, but how do we solve the problem using GraphQL, or better, can we find any examples on how to do it? Well,
either my google foo is not good, or there are no examples our there, which is the reason I an writing this post to being with. As we did with the Octokit implementation,
we are going to focus on the steps we need to perform to get to our solution:</p>

<ol>
  <li>Find a library that can do must of the work for us.</li>
  <li>Perform a GraphQL search that will return what we are looking for.</li>
  <li>Parse the results.</li>
</ol>

<h3 id="calling-graphql-from-c">Calling GraphQL from C#</h3>

<p>I am by definition a lazy developers, if there is something out there that already does the job I tend to avoid re-inventing the wheel.
Although there are no libraries that use the GitHub GraphQL endpoint in C# there is a <a href="https://github.com/graphql-dotnet/graphql-client">GraphQL.Client</a> library for dotnet.
Yes, it does no fully solve our problem but does get us there:</p>

<pre><code class="language-csharp">var personAndFilmsRequest = new GraphQLRequest {
    Query =@"
    query PersonAndFilms($id: ID) {
        person(id: $id) {
            name
            filmConnection {
                films {
                    title
                }
            }
        }
    }",
    OperationName = "PersonAndFilms",
    Variables = new {
        id = "cGVvcGxlOjE="
    }
};
</code></pre>

<p>The above is taken from their examples, we will see how we integrate that with our code alter.</p>

<h3 id="using-githubs-graphql-endpoint">Using GitHubs GraphQL endpoint</h3>

<p>There is no point of me getting into describing how GraphQL works because I am no expert (I have read the docs and used it, but no expert).</p>

<p>The best way for you to understand GraphQL is to use the <a href="https://docs.github.com/en/graphql/overview/explorer">Explorer provided by GitHub</a>. To make
your life easier, you can use the following query as a starting point:</p>

<p>Query:</p>

<pre><code class="language-graphql">query GetPRs($query: String!, $cursor: String) { 
    search(query:$query type:ISSUE after:$cursor first:100) { 
        issueCount
        edges { 
            cursor
            node { 
                ... on PullRequest { 
                    author {
                        ... on User { 
                            login 
                            name  
                        } 
                    }
                    authorAssociation
                    number 
                    title 
                    closed 
                    createdAt 
                    closedAt
                    headRefName
                    isDraft 
                    labels (last:100) { 
                        totalCount 
                        edges { 
                            node { 
                                ... on Label { 
                                    name  
                                } 
                            }
                        }
                    }
                    mergeable
                    merged 
                    mergedAt 
                    reviewRequests (last:100) { 
                        totalCount 
                    } 
                    reviews (last:100) { 
                        totalCount 
                        edges { 
                            node { 
                                ... on PullRequestReview { 
                                    state 
                                    commit {
                                        id
                                    }
                                    author { 
                                        ... on User { 
                                            login 
                                            name
                                        } 
                                    }
                                } 
                            } 
                        } 
                    } 
                    state
                    updatedAt
                } 
            } 
        }  
    } 
}

</code></pre>

<p>Variables (you need to update the values of startDateString, endDateString and repository):</p>

<pre><code class="language-graphql">{
    "query": "created:{startDateString}..{endDateString} is:pr repo:{repository}"
}
</code></pre>

<p>Now, GraphQL queries return json, but you need to understand several concepts to understand the result:</p>

<ul>
  <li>GraphQL allows to fet objects</li>
  <li>GraphQL provides a way to go through the connections.</li>
  <li>A connection is a collection of objects</li>
  <li>pageInfo contains information about the pagination of the results.</li>
  <li>An array of records is returned in an edge.</li>
  <li>An edge has nodes and a cursor</li>
</ul>

<p>The above query returns a result similar to the following (this is from one of our open source repos, do not worry):</p>

<pre><code class="language-json">{
  "data": {
    "search": {
      "issueCount": 58,
      "edges": [
        {
          "cursor": "Y3Vyc29yOjE=",
          "node": {
            "author": {
              "login": "dustin-wojciechowski",
              "name": null
            },
            "authorAssociation": "MEMBER",
            "number": 17073,
            "title": "[src] Added try catches to the methods in AVAudioPlayer to prevent Initiali…",
            "closed": false,
            "createdAt": "2022-12-15T18:46:40Z",
            "closedAt": null,
            "isDraft": true,
            "labels": {
              "totalCount": 0,
              "edges": []
            },
            "mergeable": "MERGEABLE",
            "merged": false,
            "mergedAt": null,
            "reviewRequests": {
              "totalCount": 0
            },
            "reviews": {
              "totalCount": 0,
              "edges": []
            },
            "state": "OPEN",
            "updatedAt": "2022-12-15T20:42:01Z"
          }
        },
        {
          "cursor": "Y3Vyc29yOjI=",
          "node": {
            "author": {},
            "authorAssociation": "CONTRIBUTOR",
            "number": 17072,
            "title": "[net8.0] Update dependencies from xamarin/xamarin-macios",
            "closed": false,
            "createdAt": "2022-12-15T17:59:36Z",
            "closedAt": null,
            "isDraft": false,
            "labels": {
              "totalCount": 0,
              "edges": []
            },
            "mergeable": "MERGEABLE",
            "merged": false,
            "mergedAt": null,
            "reviewRequests": {
              "totalCount": 0
            },
            "reviews": {
              "totalCount": 0,
              "edges": []
            },
            "state": "OPEN",
            "updatedAt": "2022-12-15T18:02:32Z"
          }
        },
       ...
    }
  }
}
</code></pre>

<p>At this point, we are close to have the full solution. We need to solve two problems:</p>

<ul>
  <li>Deserializing edges and nodes.</li>
  <li>Deal with pagination.</li>
</ul>

<h3 id="deserializing-graphql-responses">Deserializing GraphQL responses</h3>

<p>As I mentioned, GraphQL returns json objects, so we can use System.Text.Json to deal with the result, which is what GraphQL.Client uses. But
System.Text.Json does now know how to handle Edges and Nodes :/</p>

<p>The following generic code will help you with that, we just need an Edge and a Node class that will contain the needed information, hen just use
the generic type you need to deserialize:</p>

<pre><code class="language-csharp">public record GraphQLNode&lt;T&gt; {

	[JsonPropertyName ("node")]
	public T? Node { get; set; }

	[JsonPropertyName ("cursor")]
	public string? Cursor { get; set; }
}

public record GraphQLEdge&lt;T&gt; {
	[JsonPropertyName ("edges")]
	public GraphQLNode&lt;T&gt; [] Edges { get; set; } = Array.Empty&lt;GraphQLNode&lt;T&gt;&gt; ();

	[JsonPropertyName ("totalCount")]
	public int TotalCount { get; set; }
}
</code></pre>

<h3 id="pagination">Pagination</h3>

<p>Pagination is a pain in any language, any framework in any place.. but it has to be done and your users will be happy about it. Now, how do we deal with 
pagination in the GraphQL world and GitHub. If you go back to the qeury sample I provided you will notices that I am using a variable called <em>$cursor</em>:</p>

<pre><code class="language-graphql">query GetPRs($query: String!, $cursor: String) { 
    search(query:$query type:ISSUE after:$cursor first:100) { 
    }
}
</code></pre>

<p>This cursor is what we are going to use to let GitHub know that we want the first 100 results after a given position. 100 is not a random number, is the maximun number of results
allowed by GitHub. With that in mind, the process to follow is vry very simple:</p>

<ol>
  <li>Perform the first request with no cursor in the variables json.</li>
  <li>Receive the response, deserialize it and check the response.</li>
  <li>If TotalCount is bigger than 100, get the <em>LAST</em> node cursor and perform a second request.</li>
</ol>

<p>It is very important to pay attention and remember that you need the last node cursor, else you will get duplicated results or
if you use the first one, an infinite loop.</p>

<p>The following code uses a generic, gut shows the process that needs to be performed. I am use dotnet 7 so my interfaces have static methods, all
the rest is straight forward for the above described steps.</p>

<pre><code class="language-csharp">public async Task&lt;IEnumerable&lt;TReturn&gt;&gt; GetGraphQLSearch&lt;TSearch, TReturn&gt; (string repository, DateOnly startDate, DateOnly endDate) where TSearch : IGraphQLSearch&lt;TReturn&gt;
{
    try {
        string? cursor = null;
        int? issuesCount = null;
        int currentlyFetch = 0;
        List&lt;TReturn&gt; result = new ();
        do {
            var graphQlRequest = (cursor is null)
                ? TSearch.BuildRequest (repository, startDate, endDate)
                : TSearch.BuildRequest (repository, startDate, endDate, cursor);
            var graphQLResponse = await graphQLClient.SendQueryAsync&lt;TSearch&gt; (graphQlRequest);
            if (graphQLResponse.Errors is not null) {
                var errors = string.Join (',', graphQLResponse.Errors.Select (e =&gt; e.Message));
                Log.Error ("GraphQL Errors {@GraphQLErrors}", errors);
                return Array.Empty&lt;TReturn&gt; ();
            }

            if (graphQLResponse.Data.Search is null)
                return Array.Empty&lt;TReturn&gt; ();

            // fluent and nullability are not friends :(
            var nodes = from node in graphQLResponse.Data.Search.Edges
                        where node is not null
                        select node.Node;

            var nodesArray = nodes.ToArray ();
            result.AddRange (nodesArray);
            Log.Debug ("Received {@PRCount} from GraphQL search", nodesArray.Length);

            if (issuesCount is null) {
                Log.Debug ("GraphQL first search request for {@Repository}", repository);
                // the issue count will be the TOTAL issue count (in the first request) minus what we have in the
                // current request
                issuesCount = graphQLResponse.Data.Search.IssueCount;
                Log.Debug (
                    "GraphQL search issue count for {@Repository} is {@IssueCount} after removing {@ResponseCount}",
                    repository, issuesCount, graphQLResponse.Data.Search.Edges.Length);
            }

            currentlyFetch += graphQLResponse.Data.Search.Edges.Length;
            Log.Debug ("GraphQL search fetched {@FetchCount} out of {@IssueCount}", currentlyFetch, issuesCount);

            // if the issuesCount is not 0, set the cursor to do a second request
            if (issuesCount &gt; currentlyFetch) {
                Log.Debug ("GraphQL search remaining issues {@IssueCount}", issuesCount);
                cursor = graphQLResponse.Data.Search.Edges [^1].Cursor; // grab the last cursor
            } else {
                // we hve retrieved all pages, could be achieved by setting cursor to null, but always be
                // explicit over implicit
                Log.Debug ("GraphQL search retrieved all {@PRCount} PRs", issuesCount);
                break;
            }
        } while (cursor is not null);

        return result;
    } catch (Exception e) {
        Log.Debug ("Exception GraphQl search {@ExceptionMessage}", e.Message);
        return Array.Empty&lt;TReturn&gt; ();
    }
}

</code></pre>

<h3 id="testing">Testing</h3>

<p>The last step to have a real solution is to be able to write a test. But how do we write tests against a GRaphQL API? Well, believe it or not is not 
as hard as it sounds. T o do show what we are going to be doing is to use <a href="https://github.com/richardszalay/mockhttp">RichardSzalay.MockHttp</a> a library that can only be said to be the nest best thing
after slice bread (for testing network calls).</p>

<p>The MockHttp is the corner stone to test all the different calls. What we want to do is to create a GraphQL.Client client that uses an HttpClient that has a
mocked handler, once we have done that, we can set the expectations, and happy testing (the sample code uses xUnit):</p>

<pre><code class="language-csharp">public class TestGraphQL {
    readonly HttpClient httpClient;
	readonly GraphQLHttpClient graphQlHttpClient;
	readonly MockHttpMessageHandler mockHttpMessageHandler;
	readonly string githubGraphQLEndpoint = "https://api.github.com/graphql";

    public TestGraphQL () {
        mockHttpMessageHandler = new ();
		httpClient = new (mockHttpMessageHandler);
        // this is where the magic happens
		var options = new GraphQLHttpClientOptions { EndPoint = new (githubGraphQLEndpoint) };
		graphQlHttpClient = new (options, new SystemTextJsonSerializer (), httpClient);
    }

    [Fact]
    public async Task SampleTest ()
    {
        // set the mocker expectations
        var prData = TestsHelpers.ReadJsonFile ("graphQLPRSearch.json");
        Assert.NotNull (prData);
        mockHttpMessageHandler.When (githubGraphQLEndpoint)
            .WithPartialContent ("GetPRs")
            .Respond ("application/json", prData);

        // tet your code :) 
    }
}
</code></pre>

<p>From the above I would like to point out a few things:</p>

<ol>
  <li>You need to create the http client using the mock handler.</li>
  <li>You can not only return data, but return diff reponses. Read the docs</li>
  <li>The mock handler knows what that to return because I am using the <code>WithPartialContent</code> method with the function name of my query <code>GetPRs</code>. If you have more queries, keep that in mind.</li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>With the given exmaples, you should have all the pieces you need to perform queries against the GitHub GraphQL api using C#. In is not complicated, but there is no documentation or many example. Hope it helps.</p>]]></content><author><name>Manuel de la Peña Saenz</name></author><category term="Other" /><summary type="html"><![CDATA[I am not the most avid blogger, and I do not plan to be one. However I have decided to write some blogs/notes about programming problems I have solved to which there is not much, if any, documentation online. This post is going to focus on a small problem I found recently when trying to use octokit.net to find a collection of pull requests and their reviews.]]></summary></entry></feed>