Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mono.Android] fix trimming warnings, part 2 #8758

Merged
merged 6 commits into from
Mar 1, 2024
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
14 changes: 11 additions & 3 deletions src/Mono.Android/Android.App/Activity.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
using System;

using System.Diagnostics.CodeAnalysis;
using Android.Runtime;

namespace Android.App {

partial class Activity {

public T? FindViewById<T> (int id)
internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

public T? FindViewById<
[DynamicallyAccessedMembers (Constructors)]
T
> (int id)
where T : Android.Views.View
{
return this.FindViewById (id)!.JavaCast<T> ();
}

// See: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/Activity.java;l=3430
public T RequireViewById<T> (int id)
public T RequireViewById<
[DynamicallyAccessedMembers (Constructors)]
T
> (int id)
where T : Android.Views.View
{
var view = FindViewById<T> (id);
Expand Down
6 changes: 5 additions & 1 deletion src/Mono.Android/Android.App/Dialog.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Android.Runtime;

namespace Android.App {
Expand All @@ -8,7 +9,10 @@ public partial class Dialog {
protected Dialog (Android.Content.Context context, bool cancelable, EventHandler cancelHandler)
: this (context, cancelable, new Android.Content.IDialogInterfaceOnCancelListenerImplementor () { Handler = cancelHandler }) {}

public T? FindViewById<T> (int id)
public T? FindViewById<
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
T
> (int id)
where T : Android.Views.View
{
return this.FindViewById (id).JavaCast<T> ();
Expand Down
23 changes: 20 additions & 3 deletions src/Mono.Android/Android.App/FragmentManager.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
using Android.OS;
using Android.Runtime;
using System.Diagnostics.CodeAnalysis;

#if ANDROID_11
namespace Android.App {
public partial class FragmentManager {
public T? FindFragmentById<T> (int id) where T : Fragment
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

public T? FindFragmentById<
[DynamicallyAccessedMembers (Constructors)]
T
> (int id)
where T : Fragment
{
return FindFragmentById (id).JavaCast<T> ();
}
public T? FindFragmentByTag<T> (string tag) where T : Fragment

public T? FindFragmentByTag<
[DynamicallyAccessedMembers (Constructors)]
T
> (string tag)
where T : Fragment
{
return FindFragmentByTag (tag).JavaCast<T> ();
}
public T? GetFragment<T> (Bundle bundle, string key) where T : Fragment

public T? GetFragment<
[DynamicallyAccessedMembers (Constructors)]
T
> (Bundle bundle, string key)
where T : Fragment
{
return GetFragment (bundle, key).JavaCast<T> ();
}
Expand Down
13 changes: 10 additions & 3 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ struct JniRemappingReplacementMethod

bool jniAddNativeMethodRegistrationAttributePresent;

const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;

public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent)
{
this.jniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent;
Expand Down Expand Up @@ -473,7 +476,7 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu

public override void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
[DynamicallyAccessedMembers (MethodsAndPrivateNested)]
Type type,
string? methods) =>
RegisterNativeMembers (nativeClass, type, methods.AsSpan ());
Expand All @@ -483,7 +486,7 @@ public override void RegisterNativeMembers (
[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
public void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)] Type type,
ReadOnlySpan<char> methods)
{
try {
Expand Down Expand Up @@ -619,7 +622,11 @@ public override void WaitForGCBridgeProcessing ()
AndroidRuntimeInternal.WaitForBridgeProcessing ();
}

public override IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType)
public override IJavaPeerable? CreatePeer (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type? targetType)
{
if (!reference.IsValid)
return null;
Expand Down
5 changes: 4 additions & 1 deletion src/Mono.Android/Android.Runtime/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ namespace Android.Runtime {
public static class Extensions {

[return: NotNullIfNotNull ("instance")]
public static TResult? JavaCast<TResult> (this IJavaObject? instance)
public static TResult? JavaCast<
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
TResult
> (this IJavaObject? instance)
where TResult : class, IJavaObject
{
return Java.Interop.JavaObjectExtensions.JavaCast<TResult>(instance);
Expand Down
5 changes: 5 additions & 0 deletions src/Mono.Android/Android.Runtime/JNINativeWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
Expand Down Expand Up @@ -52,7 +53,11 @@ public static Delegate CreateDelegate (Delegate dlg)
param_types [i] = parameters [i].ParameterType;
}

// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
// IL3050 disabled in source: if someone uses NativeAOT, they will get the warning.
#pragma warning disable IL3050
var dynamic = new DynamicMethod (DynamicMethodNameCounter.GetUniqueName (), ret_type, param_types, typeof (DynamicMethodNameCounter), true);
#pragma warning restore IL3050
var ig = dynamic.GetILGenerator ();

LocalBuilder? retval = null;
Expand Down
11 changes: 10 additions & 1 deletion src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

using StackTraceElement = Java.Lang.StackTraceElement;
Expand Down Expand Up @@ -38,6 +39,14 @@ public static JavaProxyThrowable Create (Exception innerException)

void TranslateStackTrace ()
{
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
// StackFrame.GetMethod() will return null under NativeAOT;
// However, you can still get useful information from StackFrame.ToString():
// MainActivity.OnCreate() + 0x37 at offset 55 in file:line:column <filename unknown>:0:0
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "StackFrame.GetMethod() is \"best attempt\", we handle null & exceptions")]
static MethodBase? StackFrameGetMethod (StackFrame frame) =>
Comment on lines +46 to +47
Copy link
Member

Choose a reason for hiding this comment

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

Will this reason hold for actual usage?
For example - in OTel, we had a similar issue, and the outcome of the discussion was that it's very important to get something for each frame. So there we rely on .ToString() on the StackFrame which works even in NativeAOT/trimming (unless one explicitly disable stack frame info in NativeAOT as a size optimization).

I'm not saying this is wrong, just asking where is this used, and if we would like to get at least some info all the time in those scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me investigate if we could just use StackFrame.ToString() for this. It's possible we would get the same text, and the tests would pass: 1aa0ea7

Copy link
Member Author

Choose a reason for hiding this comment

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

I tested in a console app, and it appears that StackFrame.GetMethod() returns null under NativeAOT. But calling StackFrame.ToString() returns useful information that contains the method name, example:

MainActivity.OnCreate() + 0x37 at offset 55 in file:line:column <filename unknown>:0:0

To support that one day, we could parse the string, but I think delaying that work for now is best.

I left a code comment for now, linking to:

frame.GetMethod ();

var trace = new StackTrace (InnerException, fNeedFileInfo: true);
if (trace.FrameCount <= 0) {
return;
Expand All @@ -59,7 +68,7 @@ void TranslateStackTrace ()

for (int i = 0; i < frames.Length; i++) {
StackFrame managedFrame = frames[i];
MethodBase? managedMethod = managedFrame.GetMethod ();
MethodBase? managedMethod = StackFrameGetMethod (managedFrame);

var throwableFrame = new StackTraceElement (
declaringClass: managedMethod?.DeclaringType?.FullName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Android.Runtime
{
[AttributeUsage (AttributeTargets.Assembly)]
public class ResourceDesignerAttribute : Attribute
{
public ResourceDesignerAttribute (string fullName)
public ResourceDesignerAttribute (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
string fullName)
{
FullName = fullName;
}

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
public string FullName { get; set; }

public bool IsApplication { get; set; }
Expand Down
11 changes: 9 additions & 2 deletions src/Mono.Android/Android.Runtime/ResourceIdManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,19 @@ public static void UpdateIdValues ()
}
}

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Types in Resource.designer.cs are preserved, because it is the root assembly passed to the linker.")]
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
static Type? GetResourceTypeFromAssembly (Assembly assembly)
{
const string rootAssembly = "Resources.UpdateIdValues() methods are trimmed away by the LinkResourceDesigner trimmer step. This codepath is not called unless $(AndroidUseDesignerAssembly) is disabled.";

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = rootAssembly)]
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = rootAssembly)]
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
static Type AssemblyGetType (Assembly a, string name) => a.GetType (name);
Copy link
Member

Choose a reason for hiding this comment

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

Does this really work? I can't see how the trimmer would be able to determine what the returned type is and so it won't be able to preserve the public methods on it. I think at the very least the name would need to be attributed as well, but I'm not sure if that's enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I don't think the trimmer preserves anything here -- this is just ignoring the warning.

This code is looking for a Resource.designer.cs file that is generated in every app, which is the root assembly. There will likely be some problem here for NativeAOT one day.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did preserve the attribute that is kind of the "entry point":

[AttributeUsage (AttributeTargets.Assembly)]
public class ResourceDesignerAttribute : Attribute
{
	public ResourceDesignerAttribute (string fullName)
	public ResourceDesignerAttribute (
			[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
			string fullName)
	{
		FullName = fullName;
	}

	[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
	public string FullName { get; set; }

Copy link
Member

Choose a reason for hiding this comment

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

Could we add a Type property to the attribute?

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] public Type Type { get; set; }

That would be perfectly trimmable and AOT friendly.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can add a new API in .NET 9, but currently Xamarin.Android class libraries built against Mono.Android.dll from ~2012 still work. This is different from iOS, as they have breaking changes.

So there would be some number of libraries out there that won't use the new API.

Copy link
Member

Choose a reason for hiding this comment

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

This PR is for .NET 9,so we can certainly add the suggested Type property, and also have appropriate fallback code for when Type is null. (This in turn means that the new property would need to allow null, e.g. Type? Type {get;set;}.) We'd also want a "semantically meaningful" name; what is ResourceDesignerAttribute.Type supposed to mean? A better name is warranted. ResourceDesignerType, perhaps?

[assembly: Android.Runtime.ResourceDesignerAttribute(
    "Example.Android.Resource",
    ResourceDesignerType=typeof(Example.Android.Resource),
    IsApplication=true
)]

That said… the "new" Resource design of dc3ccf2 was thought/hoped to (somewhat?) address trimming support; by using properties/methods for everything the trimmer should be able to "see" what methods are used and preserve them, while allowing everything else to be trimmed away.

Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)] feels contrary to that intention? Why would all methods on the Resource type need to be preserved?

Rando aside: why'd we use a string to name the Resource type instead of typeof() in the first place? A constructor overload feels reasonable here!

partial class ResourceDesignerAttribute {
    public ResourceDesignerAttribute (Type resourceDesignerType);
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)] feels contrary to that intention? Why would all methods on the Resource type need to be preserved?

We used System.Reflection to find an UpdateIdValues() method and call it, so I think the current trimmer design is to add DynamicallyAccessedMemberTypes.PublicMethods?

partial class ResourceDesignerAttribute {
public ResourceDesignerAttribute (Type resourceDesignerType);
}

For this part, let me see what it would take to:

  1. Generate usage of this overload, as we create Resource.designer.cs at build time
  2. Maybe the old overload could emit a trimmer warning.

Copy link
Member

@jonpryor jonpryor Feb 29, 2024

Choose a reason for hiding this comment

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

@jonathanpeppers wrote:

We used System.Reflection to find an UpdateIdValues() method and call it,

Yes, we used to. Commit dc3ccf2 should have removed that call for most (all?) cases. Yes, UpdateIdValues() still exists, but "nothing" should call it (unless some customer decided to manually call it For Reasons™?). We certainly shouldn't call it, and we should be actively removing it, e.g. in ClearDesignerClass():

https://github.com/xamarin/xamarin-android/blob/e873731229274e5382af7672fa4a419f1f1d9bb1/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs#L137

@dellis1972: do we even need [assembly: ResourceDesigner(…)] anymore?

Copy link
Member

Choose a reason for hiding this comment

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

@dellis1972 replies on Discord:

Its not called if they are using the new Designer Assembly. If they turn it off then it will be used.

I overlooked/forgot that the new stuff can be disabled. It's controlled via $(AndroidUseDesignerAssembly).

When can we drop support for this? It was added in .NET 8; removing support for $(AndroidUseDesignerAssembly)=false from .NET 9 feels too early, maybe? Maybe .NET 10?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm on with .NET 10 , people should have found all the bugs in the new stuff by then :P


foreach (var customAttribute in assembly.GetCustomAttributes (typeof (ResourceDesignerAttribute), true)) {
if (customAttribute is ResourceDesignerAttribute resourceDesignerAttribute && resourceDesignerAttribute.IsApplication) {
var type = assembly.GetType (resourceDesignerAttribute.FullName);
var type = AssemblyGetType (assembly, resourceDesignerAttribute.FullName);
if (type != null)
return type;
}
Expand Down
12 changes: 10 additions & 2 deletions src/Mono.Android/Android.Views/View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public enum SystemUiFlags {

public partial class View {

internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

#if ANDROID_16
[Obsolete ("This method uses wrong enum type. Please use PerformAccessibilityAction(Action) instead.")]
public bool PerformAccessibilityAction (GlobalAction action, Bundle arguments)
Expand All @@ -22,14 +24,20 @@ public bool PerformAccessibilityAction (GlobalAction action, Bundle arguments)
}
#endif

public T? FindViewById<T> (int id)
public T? FindViewById<
[DynamicallyAccessedMembers (Constructors)]
T
> (int id)
where T : Android.Views.View
{
return this.FindViewById (id).JavaCast<T> ();
}

// See: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=25322
public T RequireViewById<T> (int id)
public T RequireViewById<
[DynamicallyAccessedMembers (Constructors)]
T
> (int id)
where T : Android.Views.View
{
var view = FindViewById<T> (id);
Expand Down
5 changes: 4 additions & 1 deletion src/Mono.Android/Android.Views/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ namespace Android.Views {

partial class Window {

public T? FindViewById<T> (int id)
public T? FindViewById<
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
T
> (int id)
where T : Android.Views.View
{
return this.FindViewById (id).JavaCast<T> ();
Expand Down
51 changes: 42 additions & 9 deletions src/Mono.Android/Java.Interop/JavaObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Java.Interop {

public static class JavaObjectExtensions {
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

[Obsolete ("Use Android.Runtime.JavaCollection.ToLocalJniHandle()")]
public static JavaCollection ToInteroperableCollection (this ICollection instance)
Expand Down Expand Up @@ -46,13 +47,19 @@ public static JavaDictionary<K,V> ToInteroperableCollection<K,V> (this IDictiona
}

[return: NotNullIfNotNull ("instance")]
public static TResult? JavaCast<TResult> (this IJavaObject? instance)
public static TResult? JavaCast<
[DynamicallyAccessedMembers (Constructors)]
TResult
> (this IJavaObject? instance)
where TResult : class, IJavaObject
{
return _JavaCast<TResult> (instance);
}

internal static TResult? _JavaCast<TResult> (this IJavaObject? instance)
internal static TResult? _JavaCast<
[DynamicallyAccessedMembers (Constructors)]
TResult
> (this IJavaObject? instance)
{
if (instance == null)
return default (TResult);
Expand All @@ -74,7 +81,10 @@ public static JavaDictionary<K,V> ToInteroperableCollection<K,V> (this IDictiona
throw new NotSupportedException (FormattableString.Invariant ($"Unable to convert type '{instance.GetType ().FullName}' to '{resultType.FullName}'."));
}

static IJavaObject CastClass (IJavaObject instance, Type resultType)
static IJavaObject CastClass (
IJavaObject instance,
[DynamicallyAccessedMembers (Constructors)]
Type resultType)
{
var klass = JNIEnv.FindClass (resultType);
try {
Expand All @@ -97,7 +107,10 @@ static IJavaObject CastClass (IJavaObject instance, Type resultType)
return (IJavaObject) TypeManager.CreateProxy (resultType, instance.Handle, JniHandleOwnership.DoNotTransfer);
}

internal static IJavaObject? JavaCast (IJavaObject? instance, Type resultType)
internal static IJavaObject? JavaCast (
IJavaObject? instance,
[DynamicallyAccessedMembers (Constructors)]
Type resultType)
{
if (resultType == null)
throw new ArgumentNullException ("resultType");
Expand All @@ -120,23 +133,43 @@ static IJavaObject CastClass (IJavaObject instance, Type resultType)

// typeof(Foo) -> FooInvoker
// typeof(Foo<>) -> FooInvoker`1
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "*Invoker types are preserved by the MarkJavaObjects linker step.")]
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = "*Invoker types are preserved by the MarkJavaObjects linker step.")]
[return: DynamicallyAccessedMembers (Constructors)]
internal static Type? GetInvokerType (Type type)
{
const string InvokerTypes = "*Invoker types are preserved by the MarkJavaObjects linker step.";

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = InvokerTypes)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type? AssemblyGetType (Assembly assembly, string typeName) =>
assembly.GetType (typeName);

// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
// IL3050 disabled in source: if someone uses NativeAOT, they will get the warning.
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2068", Justification = InvokerTypes)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type MakeGenericType (Type type, params Type [] typeArguments) =>
#pragma warning disable IL3050
type.MakeGenericType (typeArguments);
#pragma warning restore IL3050

const string suffix = "Invoker";

Type[] arguments = type.GetGenericArguments ();
if (arguments.Length == 0)
return type.Assembly.GetType (type + suffix);
return AssemblyGetType (type.Assembly, type + suffix);
Type definition = type.GetGenericTypeDefinition ();
int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal);
if (bt == -1)
throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName);
Type? suffixDefinition = definition.Assembly.GetType (
Type? suffixDefinition = AssemblyGetType (
definition.Assembly,
definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt));
if (suffixDefinition == null)
return null;
return suffixDefinition.MakeGenericType (arguments);
return MakeGenericType (suffixDefinition, arguments);
}
}
}
Loading
Loading