diff --git a/docs/website/binding_objc_libs.md b/docs/website/binding_objc_libs.md
index 0a7c2dce1047..7c7a24778b21 100644
--- a/docs/website/binding_objc_libs.md
+++ b/docs/website/binding_objc_libs.md
@@ -779,6 +779,67 @@ interface LonelyClass {
+## 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
diff --git a/docs/website/binding_types_reference_guide.md b/docs/website/binding_types_reference_guide.md
index 98fe03b4dfff..a19a987666f8 100644
--- a/docs/website/binding_types_reference_guide.md
+++ b/docs/website/binding_types_reference_guide.md
@@ -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
+
+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
diff --git a/src/error.cs b/src/error.cs
index c2810b037c30..23a517bcbcee 100644
--- a/src/error.cs
+++ b/src/error.cs
@@ -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
diff --git a/src/generator-enums.cs b/src/generator-enums.cs
index 39cee8f3dde0..19e726b4b92d 100644
--- a/src/generator-enums.cs
+++ b/src/generator-enums.cs
@@ -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)
@@ -64,6 +72,8 @@ void GenerateEnum (Type type)
}
var fields = new Dictionary ();
+ Tuple null_field = null;
+ Tuple default_symbol = null;
print ("public enum {0} : {1} {{", type.Name, GetCSharpTypeName (Enum.GetUnderlyingType (type)));
indent++;
foreach (var f in type.GetFields ()) {
@@ -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 (f, fa);
+ else
+ fields.Add (f, fa);
+ if (GetAttribute (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 (f, fa);
+ }
}
indent--;
print ("}");
@@ -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.)
@@ -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 ("}");
@@ -163,7 +190,11 @@ 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);
@@ -171,7 +202,11 @@ void GenerateEnum (Type type)
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 ("}");
}