?
Solved

Default parameter problem in C#

Posted on 2016-11-08
3
Medium Priority
?
79 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
3 Comments
 
LVL 10

Assisted Solution

by:Paweł
Paweł earned 1000 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 35

Accepted Solution

by:
ste5an earned 1000 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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
This tutorial will teach you the special effect of super speed similar to the fictional character Wally West aka "The Flash" After Shake : http://www.videocopilot.net/presets/after_shake/ All lightning effects with instructions : http://www.mediaf…
In this video you will find out how to export Office 365 mailboxes using the built in eDiscovery tool. Bear in mind that although this method might be useful in some cases, using PST files as Office 365 backup is troublesome in a long run (more on t…

765 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