Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/website/binding_objc_libs.md
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,67 @@ interface LonelyClass {
<a name="Binding_Notifications" class="injected"></a>


## Binding Enums

You can add `enum` directly in your binding files to makes it easier
to use them inside API definitions - without using a different source
file (that needs to be compiled in both the bindings and the final
project).

Example:

```
[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}

interface MyType {
[Export ("initWithEnum:")]
IntPtr Constructor (MyEnum value);
}
```

It is also possible to create your own enums to replace `NSString`
constants. In this case the generator will **automatically** create the
methods to convert enums values and NSString constants for you.

Example:

```
enum NSRunLoopMode {

[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,

[Field ("NSRunLoopCommonModes")]
Common,

[Field (null)]
Other = 1000
}

interface MyType {
[Export ("performForMode:")]
void Perform (NSString mode);

[Wrap ("Perform (mode.GetConstant ())")]
void Perform (NSRunLoopMode mode);
}
```

In the above example you could decide to decorate `void Perform (NSString mode);`
with an `[Internal]` attribute. This will **hide** the constant-based API
from your binding consumers.

However this would limit subclassing the type as the nicer API alternative
uses a `[Wrap]` attribute. Those generated methods are not `virtual`, i.e.
you won't be able to override them - which may, or not, be a good choice.

An alternative is to mark the original, `NSString`-based, definition as
`[Protected]`. This will allow subclassing to work, when required, and
the wrap'ed version will still work and call the overriden method.


## Binding Notifications

Notifications are messages that are posted to the
Expand Down
99 changes: 99 additions & 0 deletions docs/website/binding_types_reference_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,105 @@ interface MyMutableString {
}
```

# Enum Attributes

Mapping `NSString` constants to enum values is a easy way to create better
.NET API. It:

* allows code completion to be more useful, by showing **only** the correct values for the API;
* adds type safety, you cannot use another `NSString` constant in a incorrect context; and
* allows to hide some constants, making code completion show shorter API list without losing functionality.

Example:

```
enum NSRunLoopMode {

[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,

[Field ("NSRunLoopCommonModes")]
Common,

[Field (null)]
Other = 1000
}
```

From the above binding definition the generator will create the `enum` itself and will
also create a `*Extensions` static type that includes two-ways conversion methods
between the enum values and the `NSString` constants. This means the constants remains
available to developers even if they are not part of the API.

Examples:

```
// using the NSString constant in a different API / framework / 3rd party code
CallApiRequiringAnNSString (NSRunLoopMode.Default.GetConstant ());
```

```
// converting the constants from a different API / framework / 3rd party code
var constant = CallApiReturningAnNSString ();
// back into an enum value
CallApiWithEnum (NSRunLoopModeExtensions.GetValue (constant));
```

## DefaultEnumValueAttribute

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ it


You can decorate **one** enum value with this attribute. This will become the constant
being returned if the enum value is not known.

From the example above:

```
var x = (NSRunLoopMode) 99;
Call (x.GetConstant ()); // NSDefaultRunLoopMode will be used
```

If no enum value is decorated then a `NotSupportedException` will be thrown.

## ErrorDomainAttribute

Error codes are bound as an enum values. There's generally an error domain for them
and it's not always easy to find which one applies (or if one even exists).

You can use this attribute to associate the error domain with the enum itself.

Example:

```
[Native]
[ErrorDomain ("AVKitErrorDomain")]
public enum AVKitError : nint {
None = 0,
Unknown = -1000,
PictureInPictureStartFailed = -1001
}
```

You can then call the extension method `GetDomain` to get the domain constant of
any error.

## FieldAttribute

This is the same `[Field]` attribute used for constants inside type. It can also
be used inside enums to map a value with a specific constant.

A `null` value can be used to specify which enum value should be returned if a
`null` `NSString` constant is specified.

From the example above:

```
var constant = NSRunLoopMode.NewInWatchOS3; // will be null in watchOS 2.x
Call (NSRunLoopModeExtensions.GetValue (constant)); // will return 1000
```

If no `null` value is present then an `ArgumentNullException` will be thrown.


# Global Attributes

Global attributes are either applied using the `[assembly:]` attribute modifier
Expand Down
1 change: 1 addition & 0 deletions src/error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
// BI1042 Missing '[Field (LibraryName=value)]' for {field_pi.Name} (e.g."__Internal")
// BI1043 Repeated overload {mi.Name} and no [DelegateApiNameAttribute] provided to generate property name on host class.
// BI1044 Repeated name '{apiName.Name}' provided in [DelegateApiNameAttribute].
// BI1045 Only a single [DefaultEnumValue] attribute can be used inside enum {type.Name}.
// BI11xx warnings
// BI1101 Trying to use a string as a [Target]
// BI1102 Using the deprecated EventArgs for a delegate signature in {0}.{1}, please use DelegateName instead
Expand Down
49 changes: 42 additions & 7 deletions src/generator-enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public ErrorDomainAttribute (string domain)
public string ErrorDomain { get; set; }
}

[AttributeUsage (AttributeTargets.Field)]
public class DefaultEnumValueAttribute : Attribute {

public DefaultEnumValueAttribute ()
{
}
}

public partial class Generator {

static string GetCSharpTypeName (Type type)
Expand Down Expand Up @@ -64,6 +72,8 @@ void GenerateEnum (Type type)
}

var fields = new Dictionary<FieldInfo, FieldAttribute> ();
Tuple<FieldInfo, FieldAttribute> null_field = null;
Tuple<FieldInfo, FieldAttribute> default_symbol = null;
print ("public enum {0} : {1} {{", type.Name, GetCSharpTypeName (Enum.GetUnderlyingType (type)));
indent++;
foreach (var f in type.GetFields ()) {
Expand All @@ -77,7 +87,15 @@ void GenerateEnum (Type type)
continue;
if (f.IsUnavailable ())
continue;
fields.Add (f, fa);
if (fa.SymbolName == null)
null_field = new Tuple<FieldInfo, FieldAttribute> (f, fa);
else
fields.Add (f, fa);
if (GetAttribute<DefaultEnumValueAttribute> (f) != null) {
if (default_symbol != null)
throw new BindingException (1045, true, $"Only a single [DefaultEnumValue] attribute can be used inside enum {type.Name}.");
default_symbol = new Tuple<FieldInfo, FieldAttribute> (f, fa);
}
}
indent--;
print ("}");
Expand All @@ -89,7 +107,7 @@ void GenerateEnum (Type type)
// the *Extensions has the same version requirement as the enum itself
PrintPlatformAttributes (type);
print ("[CompilerGenerated]");
print ("static public class {0}Extensions {{", type.Name);
print ("static public partial class {0}Extensions {{", type.Name);
indent++;

// note: not every binding namespace will start with ns.Prefix (e.g. MonoTouch.)
Expand Down Expand Up @@ -146,13 +164,22 @@ void GenerateEnum (Type type)
print ("public static NSString GetConstant (this {0} self)", type.Name);
print ("{");
indent++;
print ("switch (self) {");
var default_symbol_name = default_symbol?.Item2.SymbolName;
foreach (var kvp in fields) {
print ("if (self == {0}.{1})", type.Name, kvp.Key.Name);
print ("case {0}.{1}:", type.Name, kvp.Key.Name);
var sn = kvp.Value.SymbolName;
if (sn == default_symbol_name)
print ("default:");
indent++;
print ("return {0};", kvp.Value.SymbolName);
print ("return {0};", sn);
indent--;
}
print ("return null;");
print ("}");
if (default_symbol_name == null) {
// note: a `[Field (null)]` does not need extra code
print ("return null;");
}
indent--;
print ("}");

Expand All @@ -163,15 +190,23 @@ void GenerateEnum (Type type)
indent++;
print ("if (constant == null)");
indent++;
print ("throw new ArgumentNullException (nameof (constant));");
// if we do not have a enum value that maps to a null field then we throw
if (null_field == null)
print ("throw new ArgumentNullException (nameof (constant));");
else
print ("return {0}.{1};", type.Name, null_field.Item1.Name);
indent--;
foreach (var kvp in fields) {
print ("else if (constant == {0})", kvp.Value.SymbolName);
indent++;
print ("return {0}.{1};", type.Name, kvp.Key.Name);
indent--;
}
print ("throw new NotSupportedException (constant + \" has no associated enum value in \" + nameof ({0}) + \" on this platform.\");", type.Name);
// if there's no default then we throw on unknown constants
if (default_symbol == null)
print ("throw new NotSupportedException (constant + \" has no associated enum value in \" + nameof ({0}) + \" on this platform.\");", type.Name);
else
print ("return {0}.{1};", type.Name, default_symbol.Item1.Name);
indent--;
print ("}");
}
Expand Down