Closed rolfbjarne closed 2 years ago
This also means that we should change precedence rules for net6-ios/tvos/macos TFMs as were designed in https://github.com/dotnet/designs/blob/main/accepted/2021/net6.0-tfms/net6.0-tfms.md.
/cc @mhutch
@marek-safar I have a PR for that: https://github.com/dotnet/designs/pull/252
If NFloat is missing things, maybe operators should be added to that? What other features need to be added to that to make it work?
Not sure if it is going to have a weird scenario next year where half the stuff is xamarin nfloat and the other is dotnet nfloat.
Can nfloat
be replace with double
in the public surface to keep things simple? nfloat does not have much advantage anymore now that almost all Apple devices are 64-bit.
This is the time to finally fix the OpenTK issue!
You are doing a binary breaking change here which was the only reason stated for why the OpenTK assembly types where not completely removed from Xamarin when this issue was raised before.
This has been an issue for more than 6 years and it would be soooooo good to finally have it fixed.
It's in the list from #5107 as Move OpenTK types out of platform assemblies
.
Thanks for this. To clarify for those unfamiliar with the Xamarin project planning/schedule, where on the .NET 6 shipping schedule does Xamarin "Preview 11" fall?
@NogginBops I believe this is point 3 in the issue description?
@jkotas
Can
nfloat
be replace withdouble
in the public surface to keep things simple? nfloat does not have much advantage anymore now that almost all Apple devices are 64-bit.
Yes, it's technically possible to expose double
instead of nfloat
, but there are still 32-bit iOS devices out there (which we support), and if we add (back) support for the Apple Watch in the future, we'll be running in 32-bit mode there too (there's no 64-bit Apple Watch).
Our feeling is that exposing double
instead of nfloat
would mean that we're not accurately mirroring the underlying platform, which can lead to unexpected behavior. One example would be if code set a double property on a 32-bit platform, and then read it back, you might get a different value back.
A more complex example would be a matrix of nfloats:
https://github.com/xamarin/xamarin-macios/blob/main/src/CoreGraphics/CGAffineTransform.cs#L39-L45
with numerous math operations defined on it, and where you might get different behavior whether we implement something in managed code:
or we P/Invoke into native code:
@mattleibow
If NFloat is missing things, maybe operators should be added to that? What other features need to be added to that to make it work?
NFloat was designed that way, so I feel it's unlikely to change: https://github.com/dotnet/runtime/issues/13788
Not sure if it is going to have a weird scenario next year where half the stuff is xamarin nfloat and the other is dotnet nfloat.
The fact that it was designed to only show up in the interop layer, and not public API, makes me hope that won't happen.
Additionally we're moving our nfloat
type out of the System
namespace to lessen the impact if it were to happen.
@NogginBops
This is the time to finally fix the OpenTK issue!
Correct, that's planned.
@Perksey
Thanks for this. To clarify for those unfamiliar with the Xamarin project planning/schedule, where on the .NET 6 shipping schedule does Xamarin "Preview 11" fall?
I can't give any specific dates, but Preview 10 will be released around the same time .NET 6 is launched (.NET Conf Nov 9th-11th), so it's the preview after that, and this fall we've been having a new preview every months or so.
Ok cool. So I guess my last question is: will the net6.0-ios
(and friends) TFM remain preview for a little while after .NET 6 GA?
Does this mean all the APIs are also being moved from usages of double
(not even System.nfloat
) to the new C# nfloat
? Similarly for int
types. I'd imagine there's quite a few overrides that need changing, if that's the case.
there's no 64-bit Apple Watch
Not strictly true, S4 and up are at least dual-core 64-bit ARM SoC (wiki). Series 3 is still 32-bit, though, and supported.
Ok cool. So I guess my last question is: will the
net6.0-ios
(and friends) TFM remain preview for a little while after .NET 6 GA?
Correct
Does this mean all the APIs are also being moved from usages of
double
(not evenSystem.nfloat
) to the new C#nfloat
?
No, we're not moving any double
or float
usages to any variety of nfloat
[1] - note that there's no C# nfloat
type (like there are C# nint
and nuint
types), there's only a System.Runtime.InteropServices.NFloat
struct in .NET 6, which, as mentioned in the description, isn't a good replacement for our System.nfloat
type.
Similarly for
int
types. I'd imagine there's quite a few overrides that need changing, if that's the case.
Yes, any code that currently uses System.nint
and System.nuint
will have to migrate to use the C# nint
and nuint
types.
However, if your code just uses nint
and nuint
, it should compile just fine without any changes.
there's no 64-bit Apple Watch
Not strictly true, S4 and up are at least dual-core 64-bit ARM SoC (wiki). Series 3 is still 32-bit, though, and supported.
Strictly speaking you're correct. It's a 64-bit CPU, but it runs in a weird 32-bit mode. Applications are basically 32-bit, and as such, we have to treat it as a 32-bit architecture.
[1]: unless it happened to be a mistake to not use nfloat
in the first place, but that's quite rare, we've been able to fix most of such mistakes already
Our feeling is that exposing double instead of nfloat would mean that we're not accurately mirroring the underlying platform, which can lead to unexpected behavior. One example would be if code set a double property on a 32-bit platform, and then read it back, you might get a different value back.
Yes, it is a trade-off. We typically opt for more native .NET look-and-feel than to precisely expose platform corner cases in .NET APIs. It is not unusual to see manual casts from double to nfloat in Xamarin code that is not a native .NET look-and-feel. Do you think it is the right trade-off to make people type these manual casts and keep learning about the special nfloat
type going forward just to avoid hitting corner cases on watch? (I am fine with you saying that it is the right tradeoff, just making sure that we are asking the right questions.)
In theory you could use System.Runtime.InteropServices.NFloat
in the internal interop code and expose double
in the public API. For 32bits support that would require the generator (or manual bindings) to add the extra cast (from float
to double
). It's likely more work today, but less work in the future (for the team). OTOH it would be easier for consumer from day 1.
WRT Handle
you could try to changes NSObject
to subclass SafeHandle
(so only one class needs to be instantiated per native object). Dealing with non-NSObject
INativeObject
types (and related runtime assuptions) will be harder... but your move toward using NativeObject
subclasses should help (and that could also subclass SafeHandle
).
Anyway have fun breaking things :-)
Move all the types in the OpenTK namespace elsewhere.
@rolfbjarne Does this mean that OpenTK will have its own registrar, possibly opening up the per-dll-registrar discussion?
If so, having a dll-per-framework
model would be great. Currently, there is no way to have only Foundation.dll
or AppKit.dll
, which would define whether the library can be run as headless, considering the ObjCRuntime is initialized, enabling proper layering of an application on top of Xamarin.Mac.
@Therzok
Move all the types in the OpenTK namespace elsewhere.
@rolfbjarne Does this mean that OpenTK will have its own registrar, possibly opening up the per-dll-registrar discussion?
No, not in the short term. The per-dll-registrar feature is unrelated to this (and rather unlikely to be completed for a while due to time constraints).
If so, having a
dll-per-framework
model would be great. Currently, there is no way to have onlyFoundation.dll
orAppKit.dll
, which would define whether the library can be run as headless, considering the ObjCRuntime is initialized, enabling proper layering of an application on top of Xamarin.Mac.
A dll per framework is something we've discussed internally in the past, but there are circular references between Apple's frameworks, which makes the implementation rather non-trivial, and the cost was deemed not worth it (which doesn't mean that it can't be brought up again at some point).
No, not in the short term. The per-dll-registrar feature is unrelated to this (and rather unlikely to be completed for a while due to time constraints).
:+1: I thought it was not going to be slotted for net6, but considered asking, since OpenTK being moved elsewhere means it has to receive static registrar support somehow.
A dll per framework is something we've discussed internally in the past, but there are circular references between Apple's frameworks, which makes the implementation rather non-trivial, and the cost was deemed not worth it (which doesn't mean that it can't be brought up again at some point).
Understood, should I file an issue for this? The split doesn't have to be per-framework, merged assemblies that need AppKit/UIKit and those that don't might be a good compromise.
@Therzok
A dll per framework is something we've discussed internally in the past, but there are circular references between Apple's frameworks, which makes the implementation rather non-trivial, and the cost was deemed not worth it (which doesn't mean that it can't be brought up again at some point).
Understood, should I file an issue for this? The split doesn't have to be per-framework, merged assemblies that need AppKit/UIKit and those that don't might be a good compromise.
Yes, that would be great.
@rolfbjarne with respect to:
[1]: System.Runtime.InteropServices.NFloat is not a good replacement for System.nfloat, because it’s a very basic type intended only for interop scenarios. For instance, it does not have any operators, so code like “NFloat + NFloat” does not compile.
I completely understand and agree with this value judgement - today. Does that still make sense with the advent of Generic Math on the horizon? Granted, it is in preview at the moment, but if it were fully flushed out, it would seem pretty trivial to have NFloat
implement all the necessary interfaces to make it a fully functional drop-in for nfloat
by implementing it through to the underlying native implementation (e.g. Single
or Double
). There might be one or two other gaps, but it would seem those are addressable by way of extension methods or simply asking to have it added to NFloat
.
Ultimately, it feels like NFloat
and nfloat
serve the same purpose, but have different features and capabilities. Wouldn't converging to one be preferable? NFloat
would seem to be the long-term winner. I concede that nfloat
might have to stay around for the short term. Backing nfloat
with NFloat
under the hood or, at least, making it convertible to and from NFloat
may set up an easier transition in the future. As an example, have interop APIs only use NFloat
, but expose nfloat
where manipulation in managed code may be necessary. That also feels like it has parity with the approach to using SafeHandle
.
"Why have one when you can have two at twice the cost?"
I think IS.NFloat not implementing any arithmetic operators was by design (@tannergooding?), so even if generic math comes along it likely will not implement those interfaces. But I could be wrong, and if that is the case then yes IS.NFloat would theoretically be a suitable replacement given we're already down the rabbit hole of ABI breaks.
@Perksey I get that it was by design - of NFloat
anyway. Clearly, it exists for the purposes of providing interop, but nothing more. By name and description, NFloat
and nfloat
are meant to do the same thing, even they have different features. Adding stuff to a core library requires a lot of thought and consideration. Once you add it, it's pretty much there forever. To that end, it's not that hard to rationalize why NFloat
is currently so feature incomplete for general usage. Creating a completely separate nfloat
that does everything NFloat
does and more seems wrong and is definitely confusing (to use).
While it may have been by design, the design of NFloat
should be revisited. If I'm way off the mark and these two types do completely different things, then the documentation - at minimum - should call out those differences (aside from obvious feature differences). The existence and use nfloat
validates there is a clear and strong use case for a fully-featured NFloat
(IMO). There could very well be other scenarios where it could be useful in constrained systems such as some IoT applications.
Ultimately, this probably isn't the right forum to discuss the historical design of NFloat
. If we agree that NFloat
should be the correct replacement for nfloat
, regardless of what its original design was, then this issue should be the catalyst to support changing/enhancing the design of NFloat
to provide the necessary capabilities. A formal discussion in the runtime repo would establish a path forward that either sees nfloat
get sunset once and for all or squash the conversation that NFloat
should be enhanced. I don't know that I'm the best person to champion to do that, but I'm more than willing to lead the charge.
At least for me, I find the answer "Well, that's just how NFloat works and it's by design" extremely unsatisfactory. If we agree that NFloat
should be the right thing, then I'm of the opinion we should push in that direction, even if it doesn't meet all the needs of today. The design of NFloat
is what it is - for the moment, but it doesn't necessarily have to stay that way.
While it may have been by design, the design of
NFloat
should be revisited.
For .NET 6 it's too late already, the API is already finalized. So in any case Xamarin will need their nfloat
on .NET 6. And I suspect even if in .NET 7 NFloat
gets matching features, Xamarin will want to keep their nfloat
to avoid API differences between framework versions (as that would be extremely confusing).
EDIT: Unless GA mobile support is being delayed to .NET 7, in which case we can go with NFloat
and push through the operators et al through API review, but that'll have to be a discussion to happen behind closed doors as I doubt "we're delaying to .NET 7" is the sort of thing MSFT would want to announce willy nilly!
Use a different type than System.IntPtr for handles (the NSObject.Handle property). Exactly which type(s) has not been decided yet. This is to avoid confusion between nint/nuint and handles.
@rolfbjarne, sorry for the late comment on this, I had missed the original issue.
I don't think this one is a good idea. The rest of .NET uses IntPtr
to represent handles and will continue doing so moving forward. As it stands "right now", IntPtr
and nint
have slightly different semantics. Even if that were to change, I still don't think it would be problematic as there are already operators/methods exposed on IntPtr
that you wouldn't want to use in the face of true opaque handles.
I think IS.NFloat not implementing any arithmetic operators was by design (@tannergooding?)
@Perksey, The reviewed design was indeed that it be purely an interchange type (this was partially influenced based on input from @jkotas, as my initial design did include basic operators, etc).
Given that nfloat
is the "ABI primitive", if it having operators or not was the deciding factor of Xamarin
using it or continuing to use their own custom version of nfloat
, then we should probably revisit this. Adding the operators wouldn't be overly complex and would be "inline" with a lot of the "generic math" changes already being done for .NET 7.
If, however, Xamarin
decided to use double
in the public surface area and NFloat
on the "backend", I think it would be fine to continue being purely an interchange type.
@Perksey You are right and I agree. @tannergooding more or less confirmed what I suspected. Here's my thoughts as to how this would play out as it relates to the breaking changes and future evolution here.
NFloat
on API surfaces
a. This assumes that NFloat
could some day replace nfloat
b. This would largely mitigate future breaking changes when/if NFloat
becomes a formal replacementnfloat
for Xamarin because NFloat
is not ready to completely replace itnfloat
and/or provide extension methods to easily convert between NFloat
and nfloat
a. Implicit conversions should be a safe like-for-like, but I'd be just as happy with explicit conversionsnfloat
is marked [Obsolete]
and perhaps even removedUsing this approach would stabilize APIs themselves to NFloat
, but provides easy-to-use conversions to nfloat
when math operations are necessary.
Having thought about it a little more, this would be my preferred and suggested approach:
NFloat
on API surfacesMathNF
like there's MathF
) that provides all of the operations, functions, and constants currently exposed by nfloat
.
a. For example: public static NFloat Add(NFloat x, NFloat y)
b. This type need not, and probably should not, live under the System
namespaceNFloat
provides intrinsic features that make it natural to use.
a. While the math class is no longer necessary it's still fully valid and doesn't need to be necessarily deprecated or removedA Math class doesn't represent the "ABI Primitive", it just provides the operations you'd do on the primitive. This path means you'd never have to worry about confusing NFloat
with nfloat
or having to deprecate/remove nfloat
at some future point in time.
This approach is a bit less than desirable for how it would be used in the short-term, but that can be mitigated in a couple of ways:
If devs care enough, they can add extension methods to make things feel more natural. For example:
internal static class NFloatExtensions
{
public static NFloat Add(this NFloat x, NFloat y) => MathNF.Add(x, y);
// ... and so
}
Not quite x + y
, but x.Add(y)
is close.
A scaffolding template can be provided that would produce a substitute NFloat
user-defined type that can be freely swapped out for IS.NFloat
at some point in the future if a developer so chooses. This is purely only if a developer wants the usage to feel natural and is probably only convenient if you're doing a lot of NFloat
math. Something like:
namespace MyProject;
using nfloat = System.Runtime.InteropServices.NFloat;
internal readonly struct NFloat : IEquatable<NFloat>
{
private readonly nfloat value;
public NFloat(nfloat value) => this.value = value;
public static implicit operator nfloat(NFloat x) => x.value;
public static implicit operator NFloat(nfloat x) => new(x);
public static NFloat operator +(NFloat x, NFloat y) => new(MathNF.Add(x.value, y.value));
public static NFloat operator -(NFloat x, NFloat y) => new(MathNF.Substract(x.value, y.value));
// ... and so on
public bool Equals(NFloat other) => value.Equals(other.value);
public override bool Equals(object? obj) => obj is NFloat other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public override string ToString() => value.ToString();
}
With the exception of the namespace, this produces source compatibility with IS.NFloat
the way we expect it to work in the future (e.g. x + y
is now supported). When it's no longer needed, you'd simply delete the file. If you never delete it, it happily lives on side-by-side - forever. Again, this would be a choice, not a requirement. This might only be something the community provides as opposed to the platform team. Regardless, this can be used to make using NFloat
today easier, while mitigating the breaking changes or obsolescence of the future.
@tannergooding
Use a different type than System.IntPtr for handles (the NSObject.Handle property). Exactly which type(s) has not been decided yet. This is to avoid confusion between nint/nuint and handles.
@rolfbjarne, sorry for the late comment on this, I had missed the original issue.
I don't think this one is a good idea.
I don't quite understand your explanation:
The rest of .NET uses
IntPtr
to represent handles and will continue doing so moving forward.
SafeHandle
(and friends) is used in a lot of places.
As it stands "right now",
IntPtr
andnint
have slightly different semantics.
To me that's a reason to use a different type than IntPtr
(which is what nint
really is) for opaque handles.
Even if that were to change, I still don't think it would be problematic as there are already operators/methods exposed on
IntPtr
that you wouldn't want to use in the face of true opaque handles.
Once again, this seems to be an argument for using a different type than IntPtr
for opaque handles.
In any case, the problem we're running into is that we're binding native API that do something like this:
void SetValue (NSInteger number);
void SetValue (NSObject* object);
when mapping this to C#, we'd end up with something like this (if using IntPtr
to represent opaque handles):
void SetValue (nint number);
void SetValue (IntPtr number);
and that won't compile, since nint
and IntPtr
are the same type.
The only remotely feasible solution to removing our ObjCRuntime.nfloat
type in favor of System.Runtime.InteropServices.NFloat
would be to if NFloat
's API was improved in .NET 7 to actually be usable.
In that case we could remove our ObjCRuntime.nfloat
type now, and app developers could either heavily adapt their existing code to use the .NET 6 version of System.Runtime.InteropServices.NFloat
, or wait until .NET 7 when the API would become a lot better.
The code adaptation in the .NET 6 time frame would be extensive and ugly...
Examples:
NFloat
from constants:// before
var color = new CGColor (0.5f, 0.25f, 0.75f);
// .NET 6
var color = new CGColor (new NFloat (0.5f), new NFloat (0.25f), new NFloat (0.75f));
// .NET 7 (would work unmodified)
var color = new CGColor (0.5f, 0.25f, 0.75f);
NFloat
math operators// before
nfloat a = 1;
nfloat b = 2;
var c = a + b;
// .NET 6
var a = new NFloat (1);
var b = new NFloat (2);
var c = MathHelper.Add (a, b);
// .NET 7 (slight modifications still required)
NFloat a = 1;
NFloat b = 2;
var c = a + b;
The (eventual) upside is that we won't have two types doing pretty much the same thing, and (🔮) we won't run into any compatibility problems if C# decides to add a nfloat
keyword.
@tannergooding: you seem to think that improving NFloat's API is feasible for .NET 7, who can I contact to get a commitment on this? Our deadline for removing nfloat
on our side is approaching fast, and we need some sort of commitment that these changes will come in .NET 7 before even starting to do any work on our side.
@tannergooding: you seem to think that improving NFloat's API is feasible for .NET 7, who can I contact to get a commitment on this?
I could likely get an commitment (or not) on Tuesday for the next API review. I'd need to write up a proposal and take it through as a "blocking" request (which isn't a big deal, it just ensures it gets reviewed "first", in relation to other blocking reviews of which we currently have none).
Based on the examples you gave above, this basically just needs operators and implicit conversions support.
One interesting thing is that you allow implicit conversion from double
in that sample. However, that is "potentially lossy" which goes against the "normal" implicit conversion rules. The safe thing would be:
double
double
float
float
That is:
// Implicit
NFloat x = 0.5f;
double y = x;
// Explicit
NFloat z = (NFloat)0.5;
float w = (float)z;
I'd also note that There are no NFloat constants,
isn't possible unless you modified the C# lang. That is, const NFloat x = 5
won't be possible because constants for user-defined structs aren't possible today. This extends to their usage in attributes, parameters (although with the right implicit conversions and attributes parameters can work, its just more verbose), etc. The implicit conversions would work, given where there is no silent truncation.
If maintaining the silent truncation support is important, I can raise that in API review, but it is "lossy" behavior.
@tannergooding
I could likely get an commitment (or not) on Tuesday for the next API review. I'd need to write up a proposal and take it through as a "blocking" request (which isn't a big deal, it just ensures it gets reviewed "first", in relation to other blocking reviews of which we currently have none).
That sounds great!
Here's our current nfloat API: https://gist.github.com/rolfbjarne/573f32cf015e0adcab3600894e2de1a2, the closer NFloat
can be the better :)
One interesting thing is that you allow
implicit conversion from double
in that sample.
That was a mistake in the sample code (which I've now changed to use float
constants), we don't support lossy conversion in our nfloat
. The double
-> nfloat
conversion is explicit:
https://gist.github.com/rolfbjarne/573f32cf015e0adcab3600894e2de1a2#file-nfloat-cs-L64
I'd also note that
There are no NFloat constants,
isn't possible unless you modified the C# lang.
Another mistake on my part, I was only thinking about conversion from constants to NFloat
. nfloat
as it stands today is in the same situation (it's not possible to have nfloat
constants).
That was a mistake in the sample code
Awesome and I did notice that when looking at the existing surface area.
I've opened https://github.com/dotnet/runtime/issues/63801 to track this and left a couple annotations. Notably there were just conversions missing to/from nuint
(UIntPtr
), the nint
/nuint
conversions are "inconsistent" with explicit/implicitness of int
/long
and uint
/ulong
, we've previously opted to not extend new types with System.IConvertible
, and Xamarin is currently using static readonly
where-as static properties
likely provide better perf and is the general "goto".
I also called out that we should likely make them inline with Half
/Double
/Single
and have it extend the generic math
interfaces that will be moving to "stable" for .NET 7. This would also expose all the math APIs like Sqrt
, Min
, Max
, etc (what looks to be currently covered by NMath
in Xamarin).
@rolfbjarne - what does this change imply for SDKs which currently target Xamarin.iOS10
? Will you have any guidance on how to fix compatibility issues?
@bgavrilMS that will continue to work as-is, there are no breaking changes if you want to target Xamarin.iOS10
. The only breaking changes are if you upgrade to .NET.
@rolfbjarne - my team owns the identity SDK. We currently target xamarin.ios10
but our customers will want to target NET6.
@bgavrilMS it's possible to target both at the same time, and ship a single NuGet that supports both.
I understand that, and we already target android9.0 and android10.0; I was hoping that there will be a guide that shows us what breaking changes there are and how to mitigate them. I am sure many SDKs will require this.
@bgavrilMS yes, we're working on a document to explain the changes and how to migrate existing code.
@rolfbjarne, I still have to write tests but https://github.com/dotnet/runtime/pull/64234 implements the necessary surface area and can be pulled down/built locally if you want to do any prototyping.
@rolfbjarne due to the changes in the type used for handles, I now get this error: https://github.com/xamarin/xamarin-macios/issues/13867
@rolfbjarne, I still have to write tests but dotnet/runtime#64234 implements the necessary surface area and can be pulled down/built locally if you want to do any prototyping.
Thanks!
All the changes we wanted to do have been implemented, so I'm closing this.
The upcoming preview 14 will have all these changes.
Barring extreme circumstances, we won't be doing any more breaking changes for subsequent preview/rc releases.
Regarding nfloat, we were able to add the operators that were missing in System.Runtime.InteropServices.NFloat
into .NET 6, so we're going to remove our System.nfloat
in favor of System.Runtime.InteropServices.NFloat
. Most code will compile without changes, because we're automatically adding a global using to make nfloat
mean System.Runtime.InteropServices.NFloat
(the most common piece of code that won't compile would be any code that references nfloat
using the full typename with namespace, such as: System.nfloat number = 123;
)
In order to fully support the new native types in C# (nint, nuint), we’ve unfortunately realized that we can’t keep compatibility with existing code or assemblies built for Xamarin.iOS or Xamarin.Mac.
The consequences are as follows when upgrading a project to .NET 6:
In addition, we’re taking the opportunity to fix a few mistakes we’ve made in the past, that we’ve been unable able to address because they were breaking changes.
These are the main changes:
nint
andnuint
types as defined by C# (they are System.IntPtr and System.UIntPtr under the hood).We also have a list of minor changes we’ve wanted to do for a long time: https://github.com/xamarin/xamarin-macios/issues/5107. We’ll be going through this list and implement the ones that make sense and that we have time to complete.
The first preview with these breaking changes will be Preview 11.
Reference: https://github.com/xamarin/xamarin-macios/issues/10508 Reference: https://github.com/dotnet/designs/pull/252 Reference: https://github.com/NuGet/Home/issues/11338
[1]: ~System.Runtime.InteropServices.NFloat is not a good replacement for System.nfloat, because it’s a very basic type intended only for interop scenarios. For instance, it does not have any operators, so code like “NFloat + NFloat” does not compile.~ The operators were implemented and back ported to .NET 6, so the concerns with System.Runtime.InteropServices.NFloat went away.