Click here to Skip to main content
15,886,919 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I am trying to create a class (Measurement) that should mimic the internal C# numeric classes (int, double, float, etc) as closely as possible. To this end I have defined the Measurement class to have its own implementations of the methods used by the C# classes.

However, I am having trouble accessing the new implementions. The code example below shows the syntax I am using.

My class includes the following in order to define the TryConvertFromChecked() method:
C#
public partial class Measurement<T> : ..., INumberBase<Measurement<T>>,...
        where T : ...,INumberBase<T>, ...
    {
...
        /// <inheritdoc cref="INumberBase{TSelf}.TryConvertFromChecked{TOther}(TOther, out TSelf)" />
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static bool INumberBase<Measurement<T>>.TryConvertFromChecked<TOther>(TOther value, [MaybeNullWhen(false)] out Measurement<T> result)
        {
            return TryConvertFrom(value, out result, ConversionOption.Checked);
        }

        private static bool TryConvertFrom<TOther>([NotNull] TOther value, [MaybeNullWhen(false)] out Measurement<T> result, ConversionOption option)
            where TOther : INumberBase<TOther>
        {
            ...
        }
...
    }


And I am trying to call that method as follows:
C#
if (!T.TryConvertFromChecked(toUnit.CompoundFactor, out var toCompoundFactor))
    {
        throw new ApplicationException($"An internal error occurred while converting the CompoundFactor for [{unitSymbol}] from type [{toUnit.CompoundFactor}] to type [{typeof(T)}]");
    }

But this is always executing the C# internal System.Numerics.INumberBase<tself> implementation. How would I get my call to TryConvertFromChecked() to call the one that I have created?

What I have tried:

C#
if (!T.Measurement<T>.TryConvertFromChecked(toUnit.CompoundFactor, out var toCompoundFactor))

Results in CS0704 Cannot do non-virtual member lookup in 'T' because it is a type parameter
Posted
Updated 9-Feb-24 10:02am
v4
Comments
[no name] 9-Feb-24 15:45pm    
Why? .NET doesn't do generic type conversions. And what are you measuring? What is the "unit of measure". I measure pixels. Then, based on the context, I get back the equivalent in yards, or paces, or inches (based on scaling). As in, Distance.FromYards(x). Or x = Distance.ToPixels().
C Pottinger 9-Feb-24 21:05pm    
It seems that .NET does do generic type conversions - at least that is what Systems.Numerics.INumberBase seems to suggest (see https://learn.microsoft.com/en-us/dotnet/api/system.numerics.inumberbase-1.tryconvertfromchecked?view=net-8.0#system-numerics-inumberbase-1-tryconvertfromchecked-1(-0-0@)).
The unit of measurement is not actually pertinent to the problem. I am just trying to find out the proper way to set up and/or call my implementation of TryConvertFromChecked.
[no name] 10-Feb-24 15:08pm    
All the system types "derive" from that base (they've done the work for you) ... In that sense it's far removed from List<t>. And you're trying to implement an (apparent) "value" interface on a "class" (yours). You should review the .NET source code for what you're trying to reproduce.
Graeme_Grant 9-Feb-24 19:03pm    
This is an issue that is difficult to diagnose here with the information given. I think that i may know what is happening, and want to make sure that I have the right solution before answering.

So, to avoid excess time digging into code, can you create a new project with just one example, not your entire code base, that demonstrates the issue? If you can, update your question with a link and then we can run and see what is happening.
C Pottinger 9-Feb-24 21:06pm    
I have created a stripped down program. Stepping through it shows that the call to TryConvertFromChecked() does not call the one defined in the code.

https://drive.google.com/file/d/1J028ZWLARdQo9xlk_RLIs8-WSyQkFMoI/view?usp=sharing

1 solution

Here is your problem:
C#
T.TryConvertFromChecked(toUnit.CompoundFactor, out var toCompoundFactor)

This calls the TryConvertFromChecked in the readonly struct Double. Here is the method:
C#
static bool INumberBase<double>.TryConvertFromChecked<TOther>(TOther value, out double result)
{
    return TryConvertFrom<TOther>(value, out result);
}

and the TryConvertFrom:
C#
private static bool TryConvertFrom<TOther>(TOther value, out double result)
    where TOther : INumberBase<TOther>
{
    // In order to reduce overall code duplication and improve the inlinabilty of these
    // methods for the corelib types we have `ConvertFrom` handle the same sign and
    // `ConvertTo` handle the opposite sign. However, since there is an uneven split
    // between signed and unsigned types, the one that handles unsigned will also
    // handle `Decimal`.
    //
    // That is, `ConvertFrom` for `double` will handle the other signed types and
    // `ConvertTo` will handle the unsigned types

    if (typeof(TOther) == typeof(Half))
    {
        Half actualValue = (Half)(object)value;
        result = (double)actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(short))
    {
        short actualValue = (short)(object)value;
        result = actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(int))
    {
        int actualValue = (int)(object)value;
        result = actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(long))
    {
        long actualValue = (long)(object)value;
        result = actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(Int128))
    {
        Int128 actualValue = (Int128)(object)value;
        result = (double)actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(nint))
    {
        nint actualValue = (nint)(object)value;
        result = actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(sbyte))
    {
        sbyte actualValue = (sbyte)(object)value;
        result = actualValue;
        return true;
    }
    else if (typeof(TOther) == typeof(float))
    {
        float actualValue = (float)(object)value;
        result = actualValue;
        return true;
    }
    else
    {
        result = default;
        return false;
    }
}

The conversion is not open for customisation in the manner that you want. you would be better off looking at using the TypeConverter Class (System.ComponentModel) | Microsoft Learn[^]

However, if you still want to go down this route, have a look at .NET Source Browser - BigInteger[^] to see how Microsoft approach it.
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900