Skip to content

C# 8 Language Features for API-30 Bindings #509

@jonpryor

Description

@jonpryor

Whenever API-30 comes out, we would like to adopt C#8 features as part of the new binding in the following ways:

  1. Default interface members for added interface & abstract methods
  2. New Java interfaces containing nested types will "remain" nested in the C# binding. Existing nested types will be "unchanged", structurally speaking.

TODO:

Determining newly added members

Within a xamarin-android/src/Mono.Android build, api-merge.exe is used to "merge" the various API XML files together so that we know in which API level a member was added. This information is in the //@merge.SourceFile attribute, e.g.

<field
    name="ACTIVITY_RECOGNITION" …
    merge.SourceFile="..\..\bin\BuildDebug\api\api-29.xml.in"
/>

Structure of existing types is not changed

Consider the android.view.MenuItem and android.view.MenuItem.OnActionExpandListener interfaces: MenuItem.OnActionExpandListener is nested within the MenuItem interface.

Versions of C# prior to C#8 didn't support nesting types within interfaces, so the C#7 binding of these types is:

partial interface IMenuItem {
}
partial interface IMenuItemOnActionExpandListener {
}

This structure will not change for existing types.

Structure of new types will require C#8 features

For types added in API-30, the types will be nested. If the Java declaration is:

package android.example;

public interface Outer {
    public static final int O = 1;
    public interface Nested {
        public static final int N = 2;
    }
}

Then the C# declaration will mirror the Java structure:

namespace Android.Example {
    public partial interface IOuter {
        public const int O = 1;
        public partial interface INested {
            public const int N = 2;
        }
    }
}

Default Interface Members to maintain compatibility

When a new abstract method is added to an abstract class, or a new required (non-default) method is added to a Java interface -- as was done with android.database.Cursor in API-11, API-19, API-23, and API-29 -- then a new default interface method should be emitted which does a throw new AbstractMethodError().

For example, consider ICursor for API-10:

partial interface ICursor {
    // ...
}

API-11 added a new Cursor.getType(int) method. If we had C#8 during API-11, we could have done:

// for API-11
partial interface ICursor {
    // … same as API-10

    // Added in API-11
    int GetType (int columnIndex)
    {
        throw new AbstractMethodError ();
    }
}

All interfaces updated to contain static members

As C#7 didn't support static members, if an interface had any static members, e.g. public static final int fields, then those fields were emitted into a class:

namespace Android.OS {
    public partial interface IParcelable : IJavaObject, IJavaPeerable {
        // …
    }
    public partial class Parcelable {
        [Register ("CONTENTS_FILE_DESCRIPTOR")]
        public const int ContentsFileDescriptor = (int) 1;

        [Register ("PARCELABLE_WRITE_RETURN_VALUE")]
        [Obsolete ("This constant will be removed in the future version. Use Android.OS.ParcelableWriteFlags enum directly instead of this field.")]
        public const Android.OS.ParcelableWriteFlags ParcelableWriteReturnValue = (Android.OS.ParcelableWriteFlags) 1;
    }
}

For compatibility, we cannot remove the Parcelable type. What we can and should do, is add all of those members to the interface as well, and make the Parcelable type [Obsolete(error:true)]

namespace Android.OS {
    public partial interface IParcelable : IJavaObject, IJavaPeerable {
        // …

        [Register ("CONTENTS_FILE_DESCRIPTOR")]
        public const int ContentsFileDescriptor = (int) 1;

        [Register ("PARCELABLE_WRITE_RETURN_VALUE")]
        public const Android.OS.ParcelableWriteFlags ParcelableWriteReturnValue = (Android.OS.ParcelableWriteFlags) 1;
    }
    [Obsolete("Use the IParcelable type", error:true)]
    public partial class Parcelable {
        [Register ("CONTENTS_FILE_DESCRIPTOR")]
        [Obsolete("Use IParcelable.ContentsFileDescriptor", error:true)]
        public const int ContentsFileDescriptor = (int) 1;

        [Register ("PARCELABLE_WRITE_RETURN_VALUE")]
        [Obsolete ("This constant will be removed in the future version. Use Android.OS.ParcelableWriteFlags.ReturnValue instead of this field.")]
        public const Android.OS.ParcelableWriteFlags ParcelableWriteReturnValue = (Android.OS.ParcelableWriteFlags) 1;
    }
}

This will "duplicate" members. We should [Obsolete] the Parcelable (and similar) types in API-30.

New members should only be added to the interface, not the "constants class". The actual constants class, ParcelableConsts, should be removed from the binding for API-30, as it will be Obsolete with error:true for over a year by the time this is expected to happen.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions