Solved

Default parameter problem in C#

Posted on 2016-11-08
3
71 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 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 34

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

Stressed Out?

Watch some penguins on the livecam!

Question has a verified solution.

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

In my previous two articles we discussed Binary Serialization (http://www.experts-exchange.com/A_4362.html) and XML Serialization (http://www.experts-exchange.com/A_4425.html). In this article we will try to know more about SOAP (Simple Object Acces…
Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
If you’ve ever visited a web page and noticed a cool font that you really liked the look of, but couldn’t figure out which font it was so that you could use it for your own work, then this video is for you! In this Micro Tutorial, you'll learn yo…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…

688 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