Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Default parameter problem in C#

Posted on 2016-11-08
3
Medium Priority
?
91 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

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

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

This document covers how to connect to SQL Server and browse its contents.  It is meant for those new to Visual Studio and/or working with Microsoft SQL Server.  It is not a guide to building SQL Server database connections in your code.  This is mo…
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
In response to a need for security and privacy, and to continue fostering an environment members can turn to for support, solutions, and education, Experts Exchange has created anonymous question capabilities. This new feature is available to our Pr…
We’ve all felt that sense of false security before—locking down external access to a database or component and feeling like we’ve done all we need to do to secure company data. But that feeling is fleeting. Attacks these days can happen in many w…

618 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