Skip to content

Replace existing Resource.designer.cs System by using Reference assemblies. #6310

Description

@dellis1972

Android Resource Assemblies

This idea is a replacement for the current Resource.designer.cs system
that exists in Xamarin.Android.

Problems with the current system.

The current implementation relies on us generating code which is then
updated via reflection on application startup. Reflection is generally
slow so it is best avoided, especially on mobile platforms.

We have another issue, the code which updates the Resouce ids must be
done for each assembly that has a Resource.desinger.cs. This means that
as you add more assemblies, your startup time will increase.

Lets use Reference Assemblies

So the new idea is going to make use of Reference assemblies. These
are assemblies which are designed be replaced at runtime.

So for class libraries what we do is instead of compiling the designer
code into the class library , we instead generate a refernce assembly
which has the Resource.designer code in it. This Resource class will
be in a KNOWN namespace. So the namespace is common across ALL assmblies.
Rather than the individually namespaced classes we have today.

For the main app we generate a normal assembly which contains ALL the resources
for the app in the Resource.designer code. This will not be a reference
assembly, it will be a standard (maybe even netstadard) assembly. This is
the only one which will be packaged in the applicaiton.

Because the class libraries are using reference assemblies, they should intheory
pick up this normal assembly at runtime.

How it will work

We will use our existing code to generate the Resource.designer.cs file, this will
then be compiled into the new assembly. For class libraries we include the
[assembly:ReferenceAssembly] attribute. The new genearted code will NOT include
the current UpdateIdValues method as it will not be required.

For apps, we do the same as above except we exclude the new attribute producing an
actual assembly. This assembly will also NOT require the UpdateIdValues method as
all the ID's in it will be the FINAL id's. Because the class exists in a specific namespace
(say Xamarin.Android.Resource.Designer) all the class libraries should be able to
resolve it.

We can then remove all the reflection code which calls UpdateIdValues.

Example

Create a netstandard2.0 class library with the following code.

using System;
using System.Runtime.CompilerServices;

[assembly:ReferenceAssembly]

namespace Xamarin.Android.Resource.Designer
{
    public class Resources
    {
        public partial class Id
		{
			public static int actions = -1;
        }
    }
}

This will generate a Reference assembly.

Next create a class library which has the following code

using System;

namespace Class1
{
    public class Class1
    {
        public int Foo () {
            return Xamarin.Android.Resource.Designer.Resources.Id.actions;
        }
    }
}

Then reference the first Reference assembly project.

Next create a netstandard2.0 library with a class with the actual desginer values.

using System;
using System.Runtime.CompilerServices;

namespace Xamarin.Android.Resource.Designer
{
    public class Resources
    {
        public partial class Id
		{
			public static int actions = 2131165207;
        }
    }
}

Next create the app program which references the class library and the FINAL designer assembly.

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine($"{Xamarin.Android.Resource.Designer.Resources.Id.actions}");
            Console.WriteLine($"{new Class1.Class1 ().Foo ()}");
        }
    }
}

This will output the following when run

Hello World!
2131165207
2131165207

As you can see the refernce assembly gets replaced by the final one. And the class library
reads the actual values.

We would need to write our own MSBuild task to generate this new assemby rather than making
a user create them, but this does prove the concept will work.

How do we migrate?

One of the issues we will have is how to deal with legacy code which exists in nuget files.

We could introduce a linker step for both relase and debug which will migrate to the new
system. This could be done by altering the existing class to derive from the new Resource
designer class, and clear out ALL of the fields. This in theory should still expose the
fields to the existing namespace.

public class Resources : Xamarin.Android.Resource.Designer.Resources {

}

Activity

  1. dellis1972 commented on Sep 20, 2021

    @dellis1972
    ContributorAuthor

    One other issue is how do we support people using Nuget packages built with the new system on older Xamarin.Android versions... do we even bother?

  2. grendello commented on Sep 20, 2021

    @grendello
    Contributor

    I think it's a great idea. The only question that pops in my mind is about the size of the final assembly, but it probably doesn't matter much since it would be the same size as it is now. For runtime performance it would be beneficial if all the classes in the assembly were both static and sealed, the JIT can generate a bit more efficient code for accessing them then.

  3. dellis1972 commented on Sep 20, 2021

    @dellis1972
    ContributorAuthor

    The RemoveResourceDesignerStep should still be able to function (with some tweaks). So we can still optimise the release builds to remove this new assembly completely.

  4. jonathanpeppers commented on Sep 20, 2021

    @jonathanpeppers
    Member

    This idea does seem like it would be better, because:

    1. ResourceIdManager and it's use of System.Reflection could go away.
    2. We don't copy lots of fields at startup.

    The issues are around: "how do we create these extra assemblies?"

    1. These assemblies have to be available during design-time builds for Intellisense. So it has to be fast & work the same as before.
    2. It doesn't seem like we should call <Csc/> independently? Does that mean we should use Mono.Cecil or System.Reflection.Metadata to generate these new assemblies with fields inside?
  5. dellis1972 commented on Sep 20, 2021

    @dellis1972
    ContributorAuthor

    We could call <Csc/> for this. I don't think it would be that much slower.
    The design-time build will still need to work and be quick, so that is something we need to be mindful of.

  6. dellis1972 commented on Sep 20, 2021

    @dellis1972
    ContributorAuthor

    I can prototype a Cecil generator and test that against a Cs call, see which is quicker.

  7. grendello commented on Sep 20, 2021

    @grendello
    Contributor

    Could the design time assembly be in memory only? Generated using System.Reflection.Emit for instance?

  8. jonathanpeppers commented on Sep 20, 2021

    @jonathanpeppers
    Member

    It might also be worth checking if type forwarding would work better for this idea than reference assemblies.

  9. 96 remaining items

  10. ghost locked as resolved and limited conversation to collaborators on Feb 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area: App+Library BuildIssues when building Library projects or Application projects.

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions