-1

Background

Having a delphi background I am used to fixing lots of thgings using constants and const arrays et all. Also Delphi allpws to have type conversions on enums using helper classes. Now take a look at these enums from C#

  public enum DateInterval { Off, Day, Month, Year };
  public enum TimeInterval { Off, MilliSecond, Second, Minute, Hour };
  public enum DateTimeInterval { Off, MilliSecond, Second, Minute, Hour, Day, Month, Year };

As you can see, there can be a logical conversion between thse enums, and I have managed to accomplish this using:

 public static class DateIntervalHelper 
    {
        public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval)
        {
            switch (aInterval)
            {
                case TimeInterval.MilliSecond:
                    return DateTimeInterval.MilliSecond;
                case TimeInterval.Second:
                    return DateTimeInterval.Second;
                case TimeInterval.Hour:
                    return DateTimeInterval.Hour;
                default: // ivOff
                    return DateTimeInterval.Off;
            }
        }
        public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
        {
            switch (aInterval)
            {
                case DateInterval.Day:
                    return DateTimeInterval.Day;
                case DateInterval.Month:
                    return DateTimeInterval.Month;
                case DateInterval.Year:
                    return DateTimeInterval.Year;
                default: // ivOff
                    return DateTimeInterval.Off;
            }
        }
    }

In delphi I would rather do something like this

const 
  cDate2DateTimeInterval:array[DateInterval] of DateTimeInterval=(
    DateTimeInterval.Off,
    DateTimeInterval.Day,
    DateTimeInterval.Month,
    DateTimeInterval.Year);

  cTime2DateTimeInterval:array[TimeInterval] of DateTimeInterval=(
    DateTimeInterval.Off,
    DateTimeInterval.MilliSecond,
    DateTimeInterval.Second,
    DateTimeInterval.Minute,
    DateTimeInterval.Hour);

And then use these arrays to "map" the conversion. (Maybe some Snytax™ errors, but you will get the point)

Question

Wat would be a cleaner way to implement this conversion in C#, using Core3.1 ?

H.Hasenack
  • 1,094
  • 8
  • 27
  • 1
    Why not just use `DateTimeInterval` for everything and save the headache? If you're going to make implicit assumptions in your conversions anyway, just be rid of them. – J... Mar 05 '21 at 15:18
  • @J... The whole point is to have different scopes for interval selection. AFAIK You cannot (like delphi can) create anenumeration type from another enumeration type (a sub-enumeration ) – H.Hasenack Mar 05 '21 at 15:33
  • So then why do you need to ever convert between them? I mean, if you've written `DoSomeFoo(TimeInterval ti)` and you're just going to call it by doing `DoSomeFoo(ConvertToTimeInterval(myDateInterval))`, then what's the point? – J... Mar 05 '21 at 15:37
  • Not sure why my Q was downvoted. But anyway: I want these enums to appear selectable values, not allowing all the other possibilities. Then I have some routines to add or calculate #intervals between dates based on the DateTimeInterval. I do not want to replicate that 3 times. That's why I need the conversion,. – H.Hasenack Mar 05 '21 at 22:04
  • Why not create [dynamic lists](https://stackoverflow.com/q/600869/327083) for the UI elements? ie: `var DateIntervals = List = { DateTimeInterval.Off, DateTimeInterval.Day, DateTimeInterval.Month, DateTimeInterval.Year }`; Not my DV, btw. – J... Mar 05 '21 at 22:16
  • @J. Yes this is true, but it's not what the Q is about. It is also a study case for me to "get to know" the quirks of C#. I have been programming delphi for 25 years, so C# needs some different thinking here and there :) – H.Hasenack Mar 05 '21 at 22:19
  • I guess I'm saying I wouldn't do it the way you're proposing at all. Not just for C# reasons, but because having disjoint semi-compatible types with loose converters seems woolly in general. A list is bindable to the UI and it naturally captures an enum subset, which is fundamentally what you wanted in the first place. If you're looking for an answer that's "the C# way", then I think something like this is probably it. – J... Mar 05 '21 at 22:20
  • @J. You are right, but it is about the conceptual way to do this kind of operation, not specifically about the DateTimeInterval or UI case. I always get confused with Foo and Bar (I am not native english speaking) – H.Hasenack Mar 05 '21 at 22:50

5 Answers5

3

This may not be the most glamorous solution but I think it has the array/map kind of feel you're talking about. Use a dictionary that maps one type to another. You could create another dictionary to go backwards by flipping the types around. Usage is shown in the "Test" method below.

public static Dictionary<DateInterval, DateTimeInterval> DateToDateTime = new Dictionary<DateInterval, DateTimeInterval>()
    {
        { DateInterval.Off, DateTimeInterval.Off},
        { DateInterval.Day, DateTimeInterval.Day},
        { DateInterval.Month, DateTimeInterval.Month},
        { DateInterval.Year, DateTimeInterval.Year}
    };

    public static void Test()
    {
        //This acts kind of like an array/map
        DateTimeInterval converted = DateToDateTime[DateInterval.Day];
    }
kd345205
  • 319
  • 1
  • 8
  • Yes that's pretty cool. But using a doctionary where values are hased over-and-over again also seems a bit overkill. – H.Hasenack Mar 05 '21 at 22:07
  • But Kd's approachdefinitely has advantages when enums are not numbered contigous. I think this approach could also work for my case using simple arrays that are initialized once in as static class, and using the (integer)input value as index in the array. (It would save on the switch statement.) – H.Hasenack Mar 05 '21 at 22:16
1

Since the name is identical you could do the following

public static class DateIntervalHelper 
{
    public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval)
    {
        if (Enum.TryParse<DateTimeInterval>(aInterval.ToString(), out var @enum))
            return @enum;

        return DateTimeInterval.Off;
    }
    public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
    {
        if (Enum.TryParse<DateTimeInterval>(aInterval.ToString(), out var @enum))
            return @enum;

        return DateTimeInterval.Off;
    }
}

Example:

https://dotnetfiddle.net/bXlBfW

Rand Random
  • 7,300
  • 10
  • 40
  • 88
1

A real nice way is to use the recently introduced EnumConverter. It's used to convert from any Enum to any other type (also another Enum). EnumConverter inherits the well known TypeConverter.

You can check the documentations here: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.enumconverter?view=netcore-3.1

Sample copy-pasted from the docs:

Enum myServer = Servers.Exchange;
string myServerString = "BizTalk";
Console.WriteLine(TypeDescriptor.GetConverter(myServer).ConvertTo(myServer, typeof(string))); 
Console.WriteLine(TypeDescriptor.GetConverter(myServer).ConvertFrom(myServerString)); 

Of course you could go the manual way, build up a Dictionary and return the the value for each key in a helper class.

The big advantage of the type/enum converter is the following: If you have some place in your code where you don't know the origin type or the target type, you can get a converter using TypeDescriptor.GetConverter and convert <U> to <T>.

Dominik
  • 1,623
  • 11
  • 27
  • It's nice, but I was hoping for a compile-time solution, reevaluating something that never changes over-and-over again seems illogical to do. – H.Hasenack Mar 05 '21 at 15:30
  • It is a "compiletime" solution. The `TypeConverter` basically is your "helper class". `TypeDescriptor.GetConverter` only creates a new instance the first time you call it. Afterwards you will get the instance created at the first call. – Dominik Mar 05 '21 at 15:37
  • That sounds interesting, will investigate. – H.Hasenack Mar 05 '21 at 22:05
1

You could write a generic converter:

static bool TryConvert<TSourceEnum, TDestEnum>(TSourceEnum source, out TDestEnum result)
    where TSourceEnum : struct, Enum
    where TDestEnum : struct, Enum
{
    if (Enum.TryParse(source.ToString(), out TDestEnum r))
    {
        result = r;
        return true;
    }
    result = default;
    return false;
}

Usage:

if (TryConvert(DateInterval.Off, out TimeInterval timeInterval))
{
    // Do something with your time interval
}

Here the string representation of the source enum value is parsed into a destination enum value.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
0

Based on Kd's answer, and still using the delphistyle mapping arrays approach (I can because because my enums are contigous) I came up with this :

public enum DateInterval { Off, Day, Month, Year };
public enum TimeInterval { Off, MilliSecond, Second, Minute, Hour };
public enum DateTimeInterval { Off, MilliSecond, Second, Minute, Hour, Day, Month, Year };


public static class DateIntervalHelper
{
    private static readonly DateTimeInterval[] dateIntervalMap = 
    { 
        DateTimeInterval.Off, DateTimeInterval.Day, DateTimeInterval.Month, DateTimeInterval.Year 
    };
    private static readonly DateTimeInterval[] timeIntervalMap = 
    { 
        DateTimeInterval.Off, DateTimeInterval.MilliSecond, DateTimeInterval.Second, DateTimeInterval.Minute, DateTimeInterval.Hour 
    };

    public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval) 
      => timeIntervalMap[(int)aInterval];
    public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
      => dateIntervalMap[(int)aInterval];
}

I actually tested this and can confirm it works :>

Using this is also easy, check overloaded functions at the end of the source:

    public static DateTime StartOfInterval(DateTime aInput, DateTimeInterval aInterval)
    {
        switch (aInterval)
        {
            case DateTimeInterval.MilliSecond:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, aInput.Second, aInput.Millisecond);
            case DateTimeInterval.Second:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, aInput.Second, 0);
            case DateTimeInterval.Minute:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, 0, 0);
            case DateTimeInterval.Hour:
                return aInput.BeginOfHour();
            case DateTimeInterval.Day:
                return aInput.BeginOfDay();
            case DateTimeInterval.Month:
                return aInput.BeginOfMonth();
            case DateTimeInterval.Year:
                return aInput.BeginOfYear();
            default: // ivOff
                return aInput;
        }
    }
    public static DateTime StartOfInterval(DateTime aInput, DateInterval aInterval)
    {
        return StartOfInterval(aInput, aInterval.ToDateTimeInterval());
    }
    public static DateTime StartOfInterval(DateTime aInput, TimeInterval aInterval)
    {
        return StartOfInterval(aInput, aInterval.ToDateTimeInterval());
    }
H.Hasenack
  • 1,094
  • 8
  • 27