name: objc-to-csharp-binding description: > Generate Xamarin.iOS / .NET for iOS C# binding API definitions from Objective-C headers by hand. Use when translating ObjC @interface, @protocol, @property, methods, enums, structs, typedefs, extern constants, blocks, and categories into C# [BaseType], [Export], [Protocol], [Model], [NullAllowed], [Field], [DesignatedInitializer], [Notification] attributed interfaces. Covers the complete dotnet/macios binding contract including type mapping, nullability, ArgumentSemantic, constructor patterns, delegate/event generation, and category handling.
Objective-C → C# Binding Definition Skill
This skill encodes the complete knowledge needed to translate Objective-C header declarations into
Xamarin.iOS / .NET for iOS C# binding API definitions (the ApiDefinition.cs contract format).
The binding contract uses C# interfaces (not classes) decorated with attributes. The binding generator (btouch / generator) turns these interfaces into actual classes at build time.
File Structure
A binding project has two key files:
| File | Contents |
|---|---|
ApiDefinition.cs |
C# interfaces with [BaseType], [Export], [Protocol] attributes — the API contract |
StructsAndEnums.cs |
Enums, structs, delegates, and supporting type definitions |
Rule: ApiDefinition.cs contains ONLY interfaces and delegate declarations. All enums, structs, and
other value types go in StructsAndEnums.cs.
Class Binding (@interface)
Basic Class
@interface MyClass : NSObject
@end
[BaseType (typeof (NSObject))]
interface MyClass
{
}
Rules:
- Every interface MUST have
[BaseType]specifying the ObjC superclass - The C# interface name matches the ObjC class name
- If the ObjC name doesn't match .NET conventions, use
[BaseType (typeof (NSObject), Name = "ObjCName")]
Class with Protocol Conformance
@interface MyClass : UIView <UITextInput, NSCoding>
@end
[BaseType (typeof (UIView))]
interface MyClass : IUITextInput, INSCoding
{
}
Rule: Protocol conformance uses I-prefixed interface names in the inheritance list.
Disabling Default Constructor
When a class marks init as NS_UNAVAILABLE:
[DisableDefaultCtor]
[BaseType (typeof (NSObject))]
interface MyClass
{
[DesignatedInitializer]
[Export ("initWithName:")]
NativeHandle Constructor (string name);
}
Property Binding (@property)
Basic Property
@property (nonatomic, copy) NSString *title;
[Export ("title")]
string Title { get; set; }
Rules:
- Property name is PascalCase in C# (the
[Export]carries the ObjC name) - The
[Export]selector is the ObjC property name (the getter selector) - The runtime auto-derives the setter as
setTitle:from the export name
ArgumentSemantic
| ObjC Attribute | C# ArgumentSemantic | When to Use |
|---|---|---|
copy |
ArgumentSemantic.Copy |
Always specify |
retain / strong |
ArgumentSemantic.Strong |
When explicitly declared |
assign |
ArgumentSemantic.Assign |
For value types or explicit |
weak |
ArgumentSemantic.Weak |
Plus [NullAllowed] |
| (none, object pointer) | (omit) | Don't infer a default |
| (none, primitive) | (omit) | Don't specify ArgumentSemantic for primitives |
| (none, enum/value type) | ArgumentSemantic.Assign |
Inferred for non-primitive value types |
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, weak) id<MyDelegate> delegate;
@property (nonatomic, assign) NSInteger count;
@property (nonatomic) UIColor *tintColor; // no explicit semantic
[Export ("name", ArgumentSemantic.Copy)]
string Name { get; set; }
[Export ("contentView", ArgumentSemantic.Strong)]
UIView ContentView { get; set; }
[NullAllowed, Export ("delegate", ArgumentSemantic.Weak)]
IMyDelegate Delegate { get; set; }
[Export ("count")]
nint Count { get; set; }
[Export ("tintColor")] // no default semantic for object pointers
UIColor TintColor { get; set; }
Readonly Properties
@property (nonatomic, readonly) NSInteger count;
[Export ("count")]
nint Count { get; }
Rule: Omit the setter for readonly properties.
Class Properties (Static)
@property (class, nonatomic, readonly) MyClass *sharedInstance;
[Static]
[Export ("sharedInstance")]
MyClass SharedInstance { get; }
Custom Getter Name
@property (nonatomic, getter=isEnabled) BOOL enabled;
[Export ("enabled")]
bool Enabled { [Bind ("isEnabled")] get; set; }
Block-Type Properties
@property (nonatomic, copy) void (^completionHandler)(BOOL success);
[Export ("completionHandler", ArgumentSemantic.Copy)]
Action<bool> CompletionHandler { get; set; }
Rule: Block properties use Action<> for void-returning blocks, Func<> for value-returning blocks.
Method Binding
Instance Methods
- (void)performAction:(NSString *)action withCompletion:(void (^)(BOOL))handler;
[Export ("performAction:withCompletion:")]
void PerformAction (string action, Action<bool> handler);
Rules:
- The
[Export]selector includes ALL colons:"performAction:withCompletion:" - Method name uses only the first selector part (PascalCased), with trailing parameter context stripped
addAnnotation:options:animated:→AddAnnotationcancelSearchAnimated:→CancelSearch(strips "Animated")- For protocol/delegate methods, strip the sender class name prefix:
myController:didSelectItem:→DidSelectItem - For single-part protocol selectors with embedded sender prefix + verb:
myViewControllerDidCancel:→DidCancel - For non-void methods with parameters that don't start with a verb, add
Getprefix:annotationForIndexPath:→GetAnnotation - Normalize acronyms: URL→Url, PDF→Pdf, HUD→Hud, HTML→Html, JSON→Json
[Async]is only added to class methods with completion handlers, never protocol methods- Constructor detection handles
nonnull instancetypeandnullable instancetype, not just bareinstancetype - Parameter names come from the ObjC parameter names
Class Methods (Static)
+ (instancetype)documentWithURL:(NSURL *)url;
[Static]
[Export ("documentWithURL:")]
MyClass FromUrl (NSUrl url);
Rule: Add [Static] before [Export] for class methods (+).
NullAllowed on Parameters
- (void)setText:(nullable NSString *)text;
[Export ("setText:")]
void SetText ([NullAllowed] string text);
NullAllowed on Return Values
- (nullable UIView *)viewForKey:(NSString *)key;
[return: NullAllowed]
[Export ("viewForKey:")]
UIView ViewForKey (string key);
Constructor Binding
Basic Constructor
- (instancetype)initWithFrame:(CGRect)frame;
[Export ("initWithFrame:")]
NativeHandle Constructor (CGRect frame);
Rules:
- Constructors return
NativeHandle(wasIntPtrin older Xamarin) - Method name is always
Constructor - The
[Export]carries the full ObjC selector
Designated Initializer
- (instancetype)initWithTitle:(NSString *)title NS_DESIGNATED_INITIALIZER;
[DesignatedInitializer]
[Export ("initWithTitle:")]
NativeHandle Constructor (string title);
Auto-Generated Constructors
The binding generator automatically creates these constructors for every bound class:
Foo ()— default, maps to ObjCinitFoo (NSCoder)— for NIB deserialization (initWithCoder:)Foo (NativeHandle)— handle-based creation (runtime use)Foo (NSEmptyFlag)— for derived classes (prevents double init)
Use [DisableDefaultCtor] to suppress the parameterless constructor.
Protocol Binding (@protocol)
Protocol with Required and Optional Methods
@protocol MyDelegate <NSObject>
@required
- (void)didFinish:(MyClass *)sender;
@optional
- (BOOL)shouldStart:(MyClass *)sender;
@end
// 1. Empty stub interface (REQUIRED for typed protocol references)
interface IMyDelegate {}
// 2. Protocol definition
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate
{
// @required → [Abstract]
[Abstract]
[Export ("didFinish:")]
void DidFinish (MyClass sender);
// @optional → no [Abstract]
[Export ("shouldStart:")]
bool ShouldStart (MyClass sender);
}
Critical Rules:
- Every
@protocolneeds an emptyinterface IProtocolName {}stub BEFORE the definition - The stub enables typed protocol references (
IMyDelegateas parameter/property type) @requiredmethods get[Abstract]@optionalmethods do NOT get[Abstract]- Protocols ending in
DelegateorDataSourceget[Protocol, Model]+[BaseType (typeof (NSObject))] - Other protocols get
[Protocol]only (no[Model], no[BaseType])
Protocol Properties — Required vs Optional
Protocol properties are handled differently based on @required vs @optional:
@protocol MyProtocol <NSObject>
@required
@property (nonatomic, readonly, getter=isActive) BOOL active;
@property (nonatomic) NSString *name;
@optional
@property (nonatomic, getter=isSelected) BOOL selected;
@property (nonatomic) NSObject *value;
@end
[Protocol]
[BaseType (typeof (NSObject))]
interface MyProtocol
{
// @required → [Abstract] + C# property (with [Bind] for custom getter)
[Abstract]
[Export ("active")]
bool Active { [Bind ("isActive")] get; }
[Abstract]
[Export ("name")]
string Name { get; set; }
// @optional → decomposed into getter/setter method pairs
[Export ("isSelected")]
bool GetSelected ();
[Export ("setSelected:")]
void SetSelected (bool selected);
[Export ("value")]
NSObject GetValue ();
[Export ("setValue:")]
void SetValue ([NullAllowed] NSObject value);
}
Critical Rules:
@requiredproperties get[Abstract]and stay as C# properties@requiredproperties with custom getters use[Bind("isX")]syntax@optionalproperties are decomposed intoGetXxx()/SetXxx()method pairs@optionalproperties with custom getters use the custom selector as the getter export (e.g.,isSelected)- Regular class (
@interface) properties are NEVER decomposed — they always use{ get; set; }
Protocol Used as Parameter Type
- (void)setDelegate:(id<MyDelegate>)delegate;
[Export ("setDelegate:")]
void SetDelegate ([NullAllowed] IMyDelegate delegate);
Rule: Use I-prefixed stub interface for protocol-typed parameters.
Delegate/Event Pattern
To generate C# events from an ObjC delegate protocol:
[BaseType (typeof (NSObject),
Delegates = new string [] { "WeakDelegate" },
Events = new Type [] { typeof (MyDelegate) })]
interface MyClass
{
[Wrap ("WeakDelegate")]
[NullAllowed]
IMyDelegate Delegate { get; set; }
[Export ("delegate", ArgumentSemantic.Assign)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate
{
[Export ("myClass:didSelectItem:"), EventArgs ("ItemSelected")]
void DidSelectItem (MyClass sender, NSObject item);
[Export ("myClassShouldClose:"), DelegateName ("Predicate<MyClass>"), DefaultValue (true)]
bool ShouldClose (MyClass sender);
}
Rules:
EventsandDelegateson[BaseType]enable C# event generation[EventArgs ("Name")]generates aNameEventArgsclass for multi-parameter events[DelegateName]+[DefaultValue]for methods that return values- Provide both
Delegate(strongly typed,[Wrap]) andWeakDelegate(weakly typed,[Export])
Nullability Rules
NS_ASSUME_NONNULL Scope
Most modern ObjC headers use:
NS_ASSUME_NONNULL_BEGIN
// Everything here is nonnull by default
NS_ASSUME_NONNULL_END
Inside NS_ASSUME_NONNULL scope:
- All object pointers are assumed nonnull → do NOT add
[NullAllowed] - Only explicitly
nullabletypes get[NullAllowed]
Outside NS_ASSUME_NONNULL scope:
- All object pointer types should get
[NullAllowed]
Nullability Annotation Mapping
| ObjC Annotation | C# Attribute |
|---|---|
nullable / _Nullable / __nullable |
[NullAllowed] |
nonnull / _Nonnull / __nonnull |
(nothing — this is the default inside NONNULL scope) |
null_unspecified |
[NullAllowed] (safer default) |
weak property |
Always [NullAllowed] (weak refs are inherently nullable) |
Where NullAllowed Goes
| Context | Placement |
|---|---|
| Property | [NullAllowed, Export ("name")] (before Export) |
| Method parameter | void Foo ([NullAllowed] string arg) (inline on parameter) |
| Return value | [return: NullAllowed] (separate attribute line) |
| Setter only | string Name { get; [NullAllowed] set; } |
Enum Binding
NS_ENUM (Native Integer Enums)
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
// In StructsAndEnums.cs
[Native]
public enum UITableViewCellStyle : long
{
Default,
Value1,
Value2,
Subtitle,
}
Rules:
NS_ENUM(NSInteger, ...)→[Native]+: longNS_ENUM(NSUInteger, ...)→[Native]+: ulong- Strip the enum name prefix from member names:
UITableViewCellStyleDefault→Default - Goes in
StructsAndEnums.cs, NOT inApiDefinition.cs
NS_OPTIONS (Flags Enums)
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
};
[Native]
[Flags]
public enum UIViewAutoresizing : ulong
{
None = 0,
FlexibleLeftMargin = 1 << 0,
FlexibleWidth = 1 << 1,
}
Enum Backing Type Mapping
| ObjC | C# |
|---|---|
NSInteger |
long (with [Native]) |
NSUInteger |
ulong (with [Native]) |
int / int32_t |
int |
unsigned int / uint32_t |
uint |
uint8_t |
byte |
String-Backed Enums
enum NSRunLoopMode
{
[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,
[Field ("NSRunLoopCommonModes")]
Common,
}
Field Binding (Extern Constants)
String Constants / Notification Names
extern NSString * const MyClassDidFinishNotification;
[Field ("MyClassDidFinishNotification")]
NSString DidFinishNotification { get; }
Rules:
- Fields go inside an interface decorated with
[Static] - For statically linked libraries, use
[Field ("name", "__Internal")] - For framework constants, use
[Field ("name", "FrameworkName")]
Notification Binding
extern NSNotificationName const MyClassDidChangeNotification;
[Notification]
[Field ("MyClassDidChangeNotification")]
NSString DidChangeNotification { get; }
This generates a Notifications nested class with ObserveDidChange() methods.
For notifications with payload:
[Notification (typeof (MyChangedEventArgs))]
[Field ("MyClassDidChangeNotification")]
NSString DidChangeNotification { get; }
// Separate interface for EventArgs
interface MyChangedEventArgs
{
[Export ("ChangedItemKey")]
NSObject ChangedItem { get; set; }
}
Category Binding
Categories That Extend Known Types (Merge)
When the parent class is in your binding, merge the category members into the parent:
// PSPDFPageView+AnnotationMenu.h
@interface PSPDFPageView (AnnotationMenu)
- (void)showMenu;
@end
// Add directly into the PSPDFPageView interface definition:
[BaseType (typeof (UIView))]
interface PSPDFPageView
{
// ... existing members ...
// From AnnotationMenu category
[Export ("showMenu")]
void ShowMenu ();
}
Rule: Modern binding convention is to merge categories into their parent class.
Categories That Extend System Types (Extension Methods)
When extending a system type like UIView or NSString:
@interface UIView (MyExtensions)
- (void)makeBackgroundRed;
@end
[Category]
[BaseType (typeof (UIView))]
interface UIView_MyExtensions
{
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
Rules:
- Use
[Category]+[BaseType (typeof (ExtendedType))] - Interface name convention:
ExtendedType_CategoryName - These become C# extension methods
- Avoid
[Static]members in categories (generates broken code — use[Category (allowStaticMembers: true)]if needed)
Type Mapping Reference
Primitive Types
| Objective-C | C# |
|---|---|
void |
void |
BOOL / bool |
bool |
char / signed char |
sbyte |
unsigned char / uint8_t |
byte |
short / int16_t |
short |
unsigned short / uint16_t |
ushort |
int / int32_t |
int |
unsigned int / uint32_t |
uint |
long |
nint |
unsigned long |
nuint |
long long / int64_t |
long |
unsigned long long / uint64_t |
ulong |
float |
float |
double |
double |
NSInteger |
nint |
NSUInteger |
nuint |
CGFloat |
nfloat |
NSTimeInterval |
double |
size_t |
nuint |
Object Types
| Objective-C | C# |
|---|---|
NSString * |
string |
NSArray * |
NSObject [] |
NSArray<Type *> |
Type [] (typed arrays) |
NSDictionary * |
NSDictionary |
NSData * |
NSData |
NSDate * |
NSDate |
NSURL * |
NSUrl |
NSError * |
NSError |
NSSet * |
NSSet |
NSNumber * |
NSNumber |
NSObject * |
NSObject |
id |
NSObject |
id<Protocol> |
IProtocol |
UIView<Protocol> |
IProtocol (protocol interface) |
instancetype |
The class type itself (for factory methods) or NativeHandle (for constructors) |
IBAction |
void |
Class |
Class |
SEL |
Selector |
*Block typedef |
*Handler (.NET convention) |
Pointer / Handle Types
| Objective-C | C# |
|---|---|
NSError ** |
out NSError |
Type ** |
out Type |
void * |
IntPtr |
CGColorRef |
CGColor |
CGPathRef |
CGPath |
CGImageRef |
CGImage |
CGContextRef |
CGContext |
SecIdentityRef |
SecIdentity |
SecTrustRef |
SecTrust |
dispatch_queue_t |
DispatchQueue |
Block Types
| Objective-C Block | C# |
|---|---|
void (^)(void) |
Action |
void (^)(BOOL) |
Action<bool> |
void (^)(NSString *, NSError *) |
Action<string, NSError> |
BOOL (^)(NSString *) |
Func<string, bool> |
id (^)(void) |
Func<NSObject> |
const char * (C Strings)
| Objective-C | C# |
|---|---|
const char * |
string (marshaled automatically) |
char * (mutable) |
IntPtr (manual marshaling) |
Variadic Parameters
- (void)appendItems:(NSObject *)firstItem, ... NS_REQUIRES_NIL_TERMINATION;
[Export ("appendItems:"), Internal]
void AppendItems (NSObject firstItem, IntPtr varArgs);
// Then provide a public wrapper using params:
// public void AppendItems (params NSObject[] items) { ... }
Struct Binding
typedef struct {
CGFloat x;
CGFloat y;
CGFloat width;
} MyRect;
// In StructsAndEnums.cs
[StructLayout (LayoutKind.Sequential)]
public struct MyRect
{
public nfloat X;
public nfloat Y;
public nfloat Width;
}
Advanced Attributes
[Wrap] — Strongly Typed Convenience
[Export ("delegate", ArgumentSemantic.Assign)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")]
[NullAllowed]
IMyDelegate Delegate { get; set; }
[Async] — Async Wrapper Generation
[Export ("fetchDataWithCompletion:")]
[Async]
void FetchData (Action<NSData, NSError> completion);
Generates: Task<NSData> FetchDataAsync ()
[Sealed] — Prevent Overriding
[Sealed]
[Export ("uniqueIdentifier")]
string UniqueIdentifier { get; }
[New] — Hide Inherited Member
[New]
[Export ("init")]
NativeHandle Constructor ();
[Internal] — Hide from Public API
[Internal]
[Export ("_privateMethod")]
void PrivateMethod ();
[Verify] — Mark for Manual Review
[Verify (MethodToProperty)]
[Export ("count")]
nint Count { get; }
Used by Objective Sharpie to flag bindings that need human review.
[BindAs] — Better Type Conversion
[return: BindAs (typeof (bool?))]
[Export ("shouldDraw:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
// Generates: bool? ShouldDraw (CGRect rect)
Common Patterns and Pitfalls
1. Missing I-Prefix Protocol Stub
Wrong: Using protocol directly without stub
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate { ... }
Right: Always add the empty stub interface
interface IMyDelegate {}
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate { ... }
2. Forgetting ArgumentSemantic for Object Properties
Wrong: Missing semantic for object pointer property
[Export ("view")]
UIView View { get; set; }
Right: Only include ArgumentSemantic when explicitly declared in ObjC property attributes
[Export ("view")]
UIView View { get; set; } // no default semantic needed
3. Wrong Selector for Multi-Part Methods
Wrong: Missing colons in selector
[Export ("setValueForKey")]
void SetValue (NSObject value, string key);
Right: Include ALL colons
[Export ("setValue:forKey:")]
void SetValue (NSObject value, string key);
4. Using int Instead of nint for NSInteger
Wrong:
[Export ("count")]
int Count { get; }
Right: NSInteger maps to nint (pointer-sized)
[Export ("count")]
nint Count { get; }
5. Enums in Wrong File
Wrong: Putting enums in ApiDefinition.cs
Right: All enums, structs, and delegates go in StructsAndEnums.cs
6. Constructor Returning Wrong Type
Wrong:
[Export ("initWithName:")]
IntPtr Constructor (string name); // Old Xamarin style
Right (modern .NET for iOS):
[Export ("initWithName:")]
NativeHandle Constructor (string name);
Vendor Macro Handling
When reading ObjC headers from third-party frameworks, ignore these common macros:
| Macro Pattern | Meaning | Action |
|---|---|---|
NS_ASSUME_NONNULL_BEGIN/END |
Nullability scope | Affects [NullAllowed] placement |
NS_DESIGNATED_INITIALIZER |
Designated init | Add [DesignatedInitializer] |
NS_REQUIRES_SUPER |
Must call super | Ignore (no C# equivalent) |
NS_SWIFT_NAME(...) |
Swift name override | Ignore |
API_AVAILABLE(...) |
Availability | Map to [Introduced] if needed |
API_DEPRECATED(...) |
Deprecation | Map to [Deprecated] if needed |
NS_UNAVAILABLE |
Unavailable init | Use [DisableDefaultCtor] |
NS_REFINED_FOR_SWIFT |
Swift refinement | Ignore |
OBJC_EXPORT / vendor export macros |
Visibility | Treat as extern |
| Other UPPER_SNAKE_CASE macros | Vendor annotations | Skip entirely |
__attribute__((...)) |
Compiler attributes | Skip entirely |
Checklist for Binding an ObjC Header
- Identify the scope: Is the header inside
NS_ASSUME_NONNULL_BEGIN/END? - Map the class:
@interface→[BaseType] interface - Map the superclass:
: NSObject→typeof (NSObject) - Map protocol conformance:
<P1, P2>→: IP1, IP2 - Map each property: type mapping, ArgumentSemantic, readonly, nullable
- Map each method: selector with colons, parameter types, return type, nullable
- Map constructors:
init*methods →NativeHandle Constructor, add[DesignatedInitializer]if marked - Map protocols:
@protocol→interface IFoo {}stub +[Protocol]definition,[Abstract]for@required - Map enums:
NS_ENUM→[Native] enum,NS_OPTIONS→[Native] [Flags] enum - Map extern constants:
extern NSString *→[Field]in[Static] interface Constants - Map categories: Merge into parent class or use
[Category]for system types - Verify nullability: Check every object pointer parameter, property, and return value
- Strip enum prefixes:
UITableViewCellStyleDefault→Default - Put types in correct files: Interfaces in ApiDefinition.cs, enums/structs in StructsAndEnums.cs
References
- Binding Types Reference Guide
- Binding Objective-C Libraries
- type-mapping.md — Complete ObjC→C# type mapping table
- attributes.md — Complete attribute reference