简体   繁体   中英

Get class properties in C# (whitout instantiating it)

I've a class "TradingStrategy", with n subclasses ("Strategy1, Strategy2 etc..."). I've a simple UI from which i can choose a subclass (I've got all the subclasses of the "TradingStrategy" class pretty easily). What i want now is to print (in a datagridview, listbox, combobox, doesn't matter) all the public parameters of the choosen subclass.

I would prefer not to instantiate the subclasses.

namespace BackTester
{
    class TradingStrategy
    {
      public string Name;
    }
    class MA_Test : TradingStrategy
    {
       new public string Name = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name;
       public int len = 12;
       public float lots = 0.1F;
       public bool trendFollow = true;

       public MA_Test()
       {

       }
   }
class MA_Test2 : TradingStrategy
    {
       new public string Name =   System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name;
       public int len = 24;
       public float lots = 0.1F;
       public bool trendFollow = true;

       public MA_Test2()
       {

       }
   }
}

With this code i can insert into a combo box every subclass of "TradingStrategy"

var type = typeof(TradingStrategy);
var types = AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(s => s.GetTypes())
                    .Where(p => type.IsAssignableFrom(p));
foreach (var t in types){
    if (t.Name == "TradingStrategy") continue;
    boxStrategy.Items.Add(t.Name);
}

I wanna be able to, from the combobox.Text, get all the properties name and values of the corrisponding subclass. I think I've read (and tried) every post here and in other forum. Many use reflections.

What is the simplest way to get those prop/values?

Thanks

Why not just create an interface ITradingStrategy:

public interface  ITradingStrategy
{
    string Name { get; }
    int len { get; }
    float lots { get; }
    bool trendFollow { get; }
}

And have all classes inherit from the interface then pull values from interface.

As was mentioned in the comments, you have to instantiate an instance of the class in order to set some values on it.

To get the public fields/properties and their types without instantiating the objects, you can use reflection as follows:

private static Dictionary<string, Type> GetFields(Type t)
{
    var fields = new Dictionary<string, Type>();

    foreach (var memberInfo in t.GetMembers(BindingFlags.Instance | BindingFlags.Public))
    {
        var propertyInfo = memberInfo as PropertyInfo;
        var fieldInfo = memberInfo as FieldInfo;
        if (propertyInfo != null)
        {
            fields.Add(propertyInfo.Name, propertyInfo.PropertyType);
        }
        if (fieldInfo != null)
        {
            fields.Add(fieldInfo.Name, fieldInfo.FieldType);
        }
    }

    return fields;
}

If you already have the object, you can get all the public fields/values with this method.

private static Dictionary<string, object> GetValues(FileInfo o)
{
    var values = new Dictionary<string, object>();

    foreach (var memberInfo in o.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public))
    {
        var propertyInfo = memberInfo as PropertyInfo;
        var fieldInfo = memberInfo as FieldInfo;
        if (propertyInfo != null)
        {
            values.Add(propertyInfo.Name, propertyInfo.GetValue(o, null));
        }
        if (fieldInfo != null)
        {
            values.Add(fieldInfo.Name, fieldInfo.GetValue(o));
        }
    }

    return values;
}

The following code is a very slow way to get all the types which derive from a given type, due to the way that the CLR implements GetTypes() and the fact there could be thousands of unrelated types in your code which makes the haystack to search even bigger. The only time you should use this method is if you dynamically load assemblies at runtime containing object definitions that you need to load. Unfortunately there is no other way to get this information at runtime:

var type = typeof(TradingStrategy);
var subtypes = AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(s => s.GetTypes())
                    .Where(p => p != type && type.IsAssignableFrom(p));

I would recommend that you store this list of types somewhere in your code, eg in an array, and iterate over it when you need to know all of your strategies:

private static readonly Type[] TradingStrategies = 
{
    typeof(Strategy1),
    typeof(Strategy2),
    typeof(Strategy3),
};

After reading Erik's answer. If you will never instantiate these classes, you could store this data in a configuration file, and use something like JSON.net to read it, or if you don't want to use an external library, XmlSerializer would work as well. In this case you would store each MATest as a Dictionary (which lends itself nicely to JSON.net 's JObject. Using JSON.net , you would have a configuration file that looks like:

[
    {
        "MA_Test": {
            "len": 12,
            "lots": 0.1,
            "trendFollow": true
        },
        "MA_Test2": {
            "len": 24,
            "lots": 0.1,
            "trendFollow": true
        }
    }
]

Then read it with code that looks like:

public JObject ReadConfig(string configPath)
{
    using (var filestream = File.Open(configPath, FileMode.Open))
    using (var streamReader = new StreamReader(filestream))
    using (var jsonTextReader = new JsonTextReader(streamReader))
    {
        var jsonSerializer = new JsonSerializer();
        return jsonSerializer.Deserialize<JObject>(jsonTextReader);
    }
}

Based on the code you've provided , there is no reason for there to be separate classes for each MA_Test ( X DO NOT use underscores, hyphens, or any other nonalphanumeric characters. ). Instead these should be the same class with different properties ( not fields ).

class TradingStrategy
{
  public string Name { get; set; }
}
class MATest : TradingStrategy
{
  // this is not needed if it is inherited by TradingStragegy
  // You should be getting a warning that you are hiding
  // the field/property
  // public string Name { get; set; }

  // Should probably be more descriptive
  // e.g. LengthInFeet...
  public int Length { get; set; }

  public float Lots { get; set; }

  // I recommended boolean properties to be prefixed with
  // Is, Can, Has, etc
  public bool CanTrendFollow { get; set; }
}

// Somewhere Else...

var MATests = new List<MATest>()
{
  new MATest()
  {
    Name = "MATest",
    Length = 12,
    Lots = 0.1F,
    CanTrendFollow = true
  },
  new MATest()
  {
    Name = "MATest",
    Length = 24,
    Lots = 0.1F,
    CanTrendFollow = true
  },
}

Now instead of costly Reflection and Activator, just create the list classes once (manually, from config or even a database), and they can be used for whatever you need.

Thank you all for you answers. The simplest way I found to get the properties from an indirected instantiated class is this:

        var strategy =   activator.CreateInstance(Type.GetType("BackTester."+boxStrategy.Text));

        foreach (FieldInfo prop in strategy.GetType().GetFields(BindingFlags.Public
            | BindingFlags.Instance))
        {
            listBox1.Items.Add(prop.ToString() + " " + prop.GetValue(strategy));
        }

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