Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 110
  • Last Modified:

Default parameter problem in C#

I have a type - defined below - but I can't seem to use it as a default parameter. What do I need to do in the type definition to allow it to be used as a default parameter.

for example:
private void foo(MyNumber2Dp pValue = 0m)
gets the error
A value of type decimal cannot be used as a default parameter because there are no standard conversions to type 'MyNumber2Dp'

In all other circumstances it seems to work with a decimal.

using System;
using System.Globalization;
// ReSharper disable SpecifyACultureInStringConversionExplicitly

#pragma warning disable 1591

namespace MyNamespace
{

    public interface IMyNumber
    {
        /// <summary>
        /// Get the number as a decimal
        /// </summary>
        /// <returns></returns>
        decimal ToDecimal();

        /// <summary>
        /// Get the number as a string
        /// </summary>
        /// <param name="culture"></param>
        /// <returns></returns>
        string ToString(CultureInfo culture);

    }

    /// <summary>
    /// Models the behaviour of an 2dp number.
    /// </summary>
    public struct MyNumber2Dp : IMyNumber
    {

        private readonly decimal value;

        /// <summary>
        /// Construct a <see cref="MyNumber2Dp"/>.
        /// </summary>
        /// <param name="value">The value which will be rounded to 2 decimal places.</param>
        public MyNumber2Dp(decimal value)
        {
            this.value = Math.Round(value, 2, MidpointRounding.AwayFromZero);
        }

        #region conversion operators

        public static implicit operator MyNumber2Dp(decimal number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(long number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(int number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(byte number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(MyNumberFloatingDp number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(MyNumber0Dp number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(MyNumber4Dp number) => new MyNumber2Dp(number);
        public static implicit operator MyNumber2Dp(MyNumber5Dp number) => new MyNumber2Dp(number);

        // It is essential that the *only* implicit conversion to another type is decimal.
        // This will ensure that all numbers are cast to decimals when processing operators that are not overloaded.
        // For the purposes of arithmetic this is essential.
        // All arithmetic operations are performed at maximum precision.
        // The final result is only rounded when it is assigned to the target variable.
        // Implementing a single implicit conversion operator will achieve the same in C#.
        public static implicit operator decimal(MyNumber2Dp number) => number.value;

        #endregion

        #region arithmetic operators

        // No arithmetic operators should not defined.
        // This will ensure the value is implicitly converted to decimal and handled by the decimal operators.
        // This is essential to ensure no loss of precision in multiplicative operations or additive operations for different types.
        // All arithmetic operations are performed at maximum precision irrespective of the variable type.
        // The final result is only rounded when it is assigned to the target variable.

        // Any defined operators are typically ambigous to the compiler since it has so many implicit conversions available to it.

        #endregion

        #region unary operators

        public static MyNumber2Dp operator -(MyNumber2Dp number) => new MyNumber2Dp(-number.value);
        public static MyNumber2Dp operator +(MyNumber2Dp number) => new MyNumber2Dp(+number.value);

        #endregion

        public decimal ToDecimal() => value;
        public string ToString(CultureInfo culture) => value.ToString(culture);
        public override string ToString() => value.ToString();

        /// <summary>Indicates whether this instance and a specified object are equal.</summary>
        /// <returns>true if <paramref name="obj" /> can be cast to a decimal and represents the same decimal value as this; otherwise, false. </returns>
        /// <param name="obj">The object to compare with the current instance. </param>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            var d = obj as decimal?;
            return d != null && d.Value == value;
        }

        public override int GetHashCode() => value.GetHashCode();

    }

}

Open in new window

0
purplesoup
Asked:
purplesoup
2 Solutions
 
PawełSharePoint DeveloperCommented:
looks like a polymorphic issue, your  MyNumber2Dp  class doesn't derive from decimal thus it can't be cast to it (it also can't because you can't inherit from structs; "I think").

as a work around just created a foo method that takes no parameters and calls you're foo(mynumber2dp ) with a value of 0 like so

    public struct MyNumber2Dp 
    {
        public decimal value { get; set; }
        public MyNumber2Dp(decimal value)
        {
            this.value = Math.Round(value, 2, MidpointRounding.AwayFromZero);
        }

        private void foo()
        {
            foo(new MyNumber2Dp(0));
        }

        private void foo(MyNumber2Dp pValue)
        {
            //your stuff
        }
    }

Open in new window



or you might be able to

   
   private void foo(MyNumber2Dp pValue = new MyNumber2Dp())
        {
            //your stuff
        }

Open in new window

0
 
ste5anSenior DeveloperCommented:
Cause C# requires an optional parameter value to be a compile time constant. This is not possible with structs, cause they need instantiation. The closest clean thing we have is a null object (static readonly struct):

namespace ConsoleCS
{
    using System;

    public class Program
    {
        static void Main(string[] args)
        {
            Foo(MyNumber2Dp.Default);
            Console.WriteLine("Done");
            Console.ReadLine();
        }

        static void Foo(MyNumber2Dp number )
        {
            Console.WriteLine(number.ToString());
        }
    }

    public struct MyNumber2Dp
    {
        private readonly decimal value;
        public MyNumber2Dp(decimal value = 0)
        {
            this.value = Math.Round(value, 2, MidpointRounding.AwayFromZero);
        }

        public static implicit operator MyNumber2Dp(decimal number) => new MyNumber2Dp(number);
        public static implicit operator decimal(MyNumber2Dp number) => number.value;
        public static MyNumber2Dp operator -(MyNumber2Dp number) => new MyNumber2Dp(-number.value);
        public static MyNumber2Dp operator +(MyNumber2Dp number) => new MyNumber2Dp(+number.value);
        public decimal ToDecimal() => value;
        public override string ToString() => value.ToString();
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            var d = obj as decimal?;
            return d != null && d.Value == value;
        }

        public override int GetHashCode() => value.GetHashCode();
        public static readonly MyNumber2Dp Default= new MyNumber2Dp(0);
    }
}

Open in new window



With using an overload it solves your problem:

public class Program
{
    static void Main(string[] args)
    {
        Foo();
        Console.WriteLine("Done");
        Console.ReadLine();
    }

    static void Foo()
    {
        Foo(MyNumber2Dp.Default);
    }

    static void Foo(MyNumber2Dp number)
    {
        Console.WriteLine(number.ToString());
    }
}

Open in new window


Caveat: Optional parameters are often not a clean OOP approach or solution. Using an overload is better. Best would be separate named method. Depending on the reason for the default, we may also consider different approaches like strategy or template.
0
 
purplesoupAuthor Commented:
Thanks for your help.
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now