简体   繁体   中英

How can I create a generic method to return a specific type specified by the call?

The following code gives me the errors:

Cannot implicitly convert type T to string.
Cannot implicitly convert type T to int.

What do I have to do to get this method to return the type of variable I define with T when I call it?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestGener234
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("his first name is {0}", GetPropertyValue<string>("firstName"));
            Console.WriteLine("his age is {0}", GetPropertyValue<int>("age"));
        }

        public static T GetPropertyValue<T>(string propertyIdCode)
        {
            if (propertyIdCode == "firstName")
                return "Jim";
            if (propertyIdCode == "age")
                return 32;
            return null;
        }
    }
}

Addendum:

Here is a more complete example of why I needed the generic solution, ie I have a class that saves its values as strings no matter what the type, and this generic solution simply makes the calling code cleaner:

using System;
using System.Collections.Generic;

namespace TestGener234
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Item> items = Item.GetItems();
            foreach (var item in items)
            {
                string firstName = item.GetPropertyValue<string>("firstName");
                int age = item.GetPropertyValue<int>("age");
                Console.WriteLine("First name is {0} and age is {1}.", firstName, age);
            }
            Console.ReadLine();
        }
    }

    public class Item
    {
        public string FirstName { get; set; }
        public string Age { get; set; }


        public static List<Item> GetItems()
        {
            List<Item> items = new List<Item>();
            items.Add(new Item { FirstName = "Jim", Age = "34" });
            items.Add(new Item { FirstName = "Angie", Age = "32" });
            return items;
        }

        public T GetPropertyValue<T>(string propertyIdCode)
        {
            if (propertyIdCode == "firstName")
                return (T)(object)FirstName;
            if (propertyIdCode == "age")
                return (T)(object)(Convert.ToInt32(Age));
            return default(T);
        }
    }
}

That is troublesome; to make the compiler happy you can double-cast, but that implies a box for value types:

    public static T GetPropertyValue<T>(string propertyIdCode)
    {
        if (propertyIdCode == "firstName")
            return (T)(object)"Jim";
        if (propertyIdCode == "age")
            return (T)(object)32;
        return default(T);
    }

In reality, I think you may be better just using an object return type.

This is an abuse of generics. If you have a small number of types that the generic type parameter could possibly be then just replace it with that many methods:

string GetTextProperty(string propertyName) { ... }
int GetNumberProperty(string propertyName) { ... }
Giraffe GetGiraffeProperty(string propertyName) { ... }

This should work...

  public static T GetPropertyValue<T>(string propertyIdCode)
  {
     if (propertyIdCode == "firstName")
        return (T)Convert.ChangeType("Jim", typeof(T));
     if (propertyIdCode == "age")
        return (T)Convert.ChangeType(22, typeof(T));
     return default(T);
  }

GetPropertyValue<string>("age") wants to return a string. Change it to GetPropertyValue<int>("age") and it will work as long as "age" is your parameter value.

Your implementation would be better off getting the type of the generic parameter T in order to choose what to return instead of basing it on the function parameter.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestGener234
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("his first name is {0}", GetPropertyValue<string>("firstName"));
            Console.WriteLine("his age is {0}", GetPropertyValue<int>("age"));
        }

        public static T GetPropertyValue<T>(string propertyIdCode)
        {
            if (typeof(T) == typeof(string) && propertyIdCode == "firstName")
                return "Jim";
            if (typeof(T) == typeof(string) && propertyIdCode == "age")
                return "32";
            if (typeof(T) == typeof(int) && propertyIdCode == "age")
                return 32;
            throw (new ArgumentException());
        }
    }
}

You can return object from GetPropertyValue and then do a cast. You are trying to use a generic method to return specific types depending on input parameters. Sounds confusing :-)

public static object GetPropertyValue(string propertyIdCode)
    {
        if (propertyIdCode == "firstName")
            return "Jim";
        if (propertyIdCode == "age")
            return 32;
        return null;
    }

and then cast (int)GetPropertyValue("age");

Usually when you are casting inside a generic method, it is a design problem. Usually, you want to keep your type generic inside your method ( no casting, no braching based on type ), something like this:

public class Properties<T>
{
    private Dictionary<string, T> _dict = new Dictionary<string, T>();

    public void SetPropertyValue<T>(string propertyIdCode, T value)
    {
        _dict[propertyIdCode] = value;
    }

    public T GetPropertyValue<T>(string propertyIdCode)
    {
        return _dict[propertyIdCode];
    }
}

On, the other hand, if you want to access object's properties through their name (it seems like this is what you are doing, sorry if I got it wrong), the right way would be to use reflection ( PropertyInfo.GetValue ):

public object GetPropertyValue(object obj, string propertyIdCode)
{
    PropertyInfo pinfo = obj.GetType().GetProperty(propertyIdCode);
    return pinfo.GetValue(obj, null);
}
    public static T GetPropertyValue<T>(string propertyIdCode)
    {
        object result = null;
        if (propertyIdCode == "firstName")
            result = "Jim";
        if (propertyIdCode == "age")
            result = 32;
        return result == null ? default(T) : (T)result;
    }

Marc's example of double-casting is the correct way to get the compiler to behave correctly.

You could write a sperate method for each value type and have a generic method for reference types. This would stop stop boxing on value types.

This is only useful if the objects being accessed are not boxed for storage (eg not stored as an object).

public static T GetPropertyValue<T>(string propertyIdCode)
{
}

public static int GetPropertyInt(string propertyIdCode)
{
}

还有另一种方法 - 使用Convert.ChangeType:

CType(Convert.ChangeType(mItem, GetType(TItem)), TItem)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM