Solved

Default parameter problem in C#

Posted on 2016-11-08
3
27 Views
Last Modified: 2016-11-08
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
Comment
Question by:purplesoup
3 Comments
 
LVL 8

Assisted Solution

by:Paweł
Paweł earned 250 total points
ID: 41878667
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
 
LVL 32

Accepted Solution

by:
ste5an earned 250 total points
ID: 41878722
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
 

Author Closing Comment

by:purplesoup
ID: 41878897
Thanks for your help.
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

8 Experts available now in Live!

Get 1:1 Help Now