2

I have an Option<T> that works great for any type that be converted from string, and now I am trying to extend that to cover Option<T[]> (ie, Option<int[]>). Afraid I may be coming at this problem with too many C++ templates under my belt. Having trouble wrapping my head around the seemingly inadequate C# generics. I can detect when T is an array, but then I can't make any use of typeof(T).GetElementType().

I think I may be in one of those XY problem valleys, where I am just coming at this from the wrong direction and can't see the path over the rise. Any ideas how to get unbocked? I've tried everything I can think of and spend the past couple of days trying to figure out to get unblocked. I would add that I can arrange to parse the comma delimited string into an array of strings prior to conversion. The code below is a simplified extract from some of what I've tried.

using System;
using System.Collections.Generic;

namespace StackOverflowCS
{
    internal static class ConversionExtensionMethods
    {
        internal static T ChangeType<T>(this object obj)
        {
            try
            {
                return (T)Convert.ChangeType(obj, typeof(T));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        internal static T ChangeType<T>(this object[] objects)
        {
            try
            {
                if (!typeof(T).IsArray) throw new Exception("T is not an array type.");

                var converted = new object[objects.Length];

                foreach (var item in objects)
                {
                    // AFAIK, converstion requires compile time knowledge of T.GetElementType(),
                    // but this won't compile.
                    converted.Add(item.ChangeType<typeof(T).GetElementType())>
                }

                return (T)converted; // And this won't compile either.
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }


    internal class Option<T>
    {
        public T Value;

        public Option() {}

        // This works fine for non-arrays
        public bool SetValue(string valueString)
        {
            try
            {
                Value = valueString.ChangeType<T>();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return false;
            }

            return true;
        }

        // I think I am in an XY problem valley here.
        public bool SetValue(string[] valueStrings)
        {
            try
            {
                if (!typeof(T).IsArray)
                {
                    throw new Exception("T is not an array type.");
                }

                // The crux of my problem is I can't seem to write pure generic code in C#
                var convertedElements = new List<!!!Cannot use typeof(T).GetElementType() here!!!>();

                foreach (var item in valueStrings)
                {
                    // The crux of my problem is I can't seem to write pure generic code in C#
                    convertedElements.Add(!!!Cannot use typeof(T).GetElementType() here!!!);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return false;
            }

            return true;
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            var opt = new Option<int>(); // Works fine.

            var integerList = new Option<int[]>();

            integerList.SetValue("this,that,whatever"); // This fails at run-time.

            foreach (var item in integerList.Value)
            {
                Console.WriteLine(item);
            }


            Console.ReadKey();
        }
        
    }
}

The parser (not shown) can detect arguments of the form
Opt:1,2,3 or
Opt:"short sentence",word,"string with quotes\" in it", etc.
I'd rather not have the parser try to figure out what type the Opt Option's array elements are. The Option<T>.SetValue(string[] strings) function should be able to handle that.
I haven't even tried test/implement `Options<List> yet, though I suspect that would be much easier.

18
  • 2
    the point of generics is that you do not need to switch based on the actual type. Instead the generic members works for all types (satisifying the generic constraint of course). So if your method does different things based on different types, you should have different methods in the first place. Furthermore generics are a compile-time-thing. You have to know the actual time at compile-time. Commented Sep 15, 2020 at 5:59
  • 1
    I honestly have no clue about C++. That is how generics work in C# works. Commented Sep 15, 2020 at 6:03
  • 1
    C++ templates are not the same thing than C# generics. Generics comes from templates, strongly typed templates and fully OOP. Hence generics can do more and less than templates. Commented Sep 15, 2020 at 6:05
  • 1
    Third, what would you expect the result of the following code to be? integerList.SetValue("1,2,NotANumber"); Should the array contains 1 and 2, or should it be null (or uninitialized)? Fourth, What if the string is delimited using ; or ` ` (space) or | or whatever other char the user of your code thinks is a good delimiter? Commented Sep 15, 2020 at 6:15
  • 2
    And one more thing - integerList.SetValue("this,that,whatever"); will call this overload: SetValue(string valueString) - you probably meant to use SetValue(string[] valueString) which means you have to do something like this: integerList.SetValue(new [] {"this","that","whatever"}); Commented Sep 15, 2020 at 6:20

1 Answer 1

2

You can try this to allow both non-array and array generic types parameters:

using System;
using System.Linq;

SetValue(string valueString)

public bool SetValue(string valueString)
{
  try
  {
    if ( typeof(T).IsArray ) throw new Exception("T is an array type.");
    Value = (T)Convert.ChangeType(valueString, typeof(T));
  }
  catch ( Exception e )
  {
    Console.WriteLine(e);
    return false;
  }
  return true;
}

SetValue(string[] valueStrings)

public bool SetValue(string[] valueStrings)
{
  try
  {
    if ( !typeof(T).IsArray ) throw new Exception("T is not an array type.");
    var thetype = typeof(T).GetElementType();
    var list = valueStrings.Select(s => Convert.ChangeType(s, thetype)).ToList();
    var array = Array.CreateInstance(thetype, list.Count);
    for (int index = 0; index < list.Count; index++ )
      array.SetValue(list[index], index);
    Value = (T)Convert.ChangeType(array, typeof(T));
  }
  catch ( Exception e )
  {
    Console.WriteLine(e);
    return false;
  }
  return true;
}

The test

static void Main(string[] args)
{
  // Non array
  var opt = new Option<int>();
  opt.SetValue("10");
  Console.WriteLine(opt.Value);
  Console.WriteLine();
  // Array
  var integerList = new Option<int[]>();
  integerList.SetValue(new[] { "1", "2", "3" });
  foreach ( var item in integerList.Value )
    Console.WriteLine(item);
  // End
  Console.ReadKey();
}

Output

10

1
2
3
Sign up to request clarification or add additional context in comments.

6 Comments

Awesome! var array = Array.CreateInstance(thetype, list.Count); was the conceptual bit I was missing. I can't believe I thrashed about for two days over this one. Thank you so much!
Why not simply Value = valueStrings.Select(s => Convert.ChangeType(s, thetype)).ToArray();?
It does not work because it can't cast... I tried many things until found this solution.
Ah, yes, the Convert.ChangeType returns object, forgot about that part for a minute there....
I think the problem here, is that typeof(T).GetElementType() cannot be resolved at compile time. If it could, then a pure generic solution would have been available. Am I wrong?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.