C#使用反射创建结构体。

15

我目前正在使用C#的反射编写代码,将通用对象保存到XML中。

问题是,在读取XML时一些对象是结构体,我无法弄清楚如何初始化这个结构体。对于类,我可以使用

ConstructorInfo constructor = SomeClass.GetConstructor(Type.EmptyTypes);

然而,对于一个结构体来说,没有不带参数的构造函数,因此上述代码将构造函数设置为null。我也尝试过

SomeStruct.TypeInitializer.Invoke(null)

但是这会抛出一个MemberAccessException异常。谷歌没有给出有希望的结果。任何帮助都将不胜感激。

3个回答

21
如果这些值是结构体,它们很可能是不可变的 - 因此您不应该调用无参数构造函数,而是应该使用带有适当值的构造函数参数。如果这些结构体不是不可变的,则要尽可能快地避开它们...但如果您绝对必须这样做,那么请使用Activator.CreateInstance(SomeClass)。但是,在使用反射设置值类型的属性或字段时,您必须非常小心 - 如果没有这种小心,您将创建一个副本,更改该副本上的值,然后将其丢弃。我怀疑如果您在整个过程中使用装箱版本,则可以解决问题:
using System;

// Mutable structs - just say no...
public struct Foo
{
    public string Text { get; set; }
}

public class Test
{
    static void Main()
    {
        Type type = typeof(Foo);

        object value = Activator.CreateInstance(type);
        var property = type.GetProperty("Text");
        property.SetValue(value, "hello", null);

        Foo foo = (Foo) value;
        Console.WriteLine(foo.Text);
    }
}

在史诗级的帖子中发帖...此外,点赞简洁、清晰的答案。 - Shotgun Ninja
问题在于现在,Activator.CreateInstance返回的是RuntimeType而不是我要求的类型 :( 这意味着GetFields什么也没有返回。 - Marcel Popescu
@Marcel:听起来你应该用 [mcve] 提一个新问题。 - Jon Skeet
我的错;尝试“查找”类型是问题所在,而不是这个。如果我无法解决这个问题,我会发布一个问题。 - Marcel Popescu
如果结构体是 byte 类型怎么办?由于它是不可变的,我不能在创建实例后直接设置其值。如何使用 Activator 设置其值? - Michael Haddad
@Sipo:对于这种情况,通常会为不可变结构调用适当的构造函数,因此你不需要这样做。但对于原始值,我希望你能够直接进行类型转换。不过,如果没有更多信息,很难做出更多解释。看起来你应该根据具体情况提出一个新问题。 - Jon Skeet

7
CreateInstance 方法无法帮助您处理没有明确定义构造函数的结构体。
FormatterServices.GetUninitializedObject(Type type);

这可以通过使用空结构体来实现。

不是真的。如果你有一个没有构造函数的结构体,Activator.CreateInstance 就可以正常工作。 - Dmitri Nesteruk

0

补充一下 - 对于不可变结构体,你可能需要对构造函数进行参数匹配。不幸的是,当存在多个构造函数时,这很棘手,特别是某些类型具有单独的静态“Create”方法而不是公共构造函数。但是假设你已经完成了匹配,仍然可以使用Activator.CreateInstance

    Type type = typeof(Padding); // just an example
    object[] args = new object[] {1,2,3,4};
    object obj = Activator.CreateInstance(type, args);

然而,选择构造函数的代码(上面有3个...)并不容易。你可以说“选择最复杂的”,然后尝试将参数名称与属性名称(不区分大小写)进行匹配...

一个天真的例子:

static void Main() {
    Dictionary<string, object> propertyBag =
        new Dictionary<string, object>();
    // these are the values from your xml
    propertyBag["Left"] = 1;
    propertyBag["Top"] = 2;
    propertyBag["Right"] = 3;
    propertyBag["Bottom"] = 4;
    // the type to create
    Type type = typeof(Padding);

    object obj = CreateObject(type, propertyBag);

}
static object CreateObject(Type type, IDictionary<string,object> propertyBag)
{
    ConstructorInfo[] ctors = type.GetConstructors();
    // clone the property bag and make it case insensitive
    propertyBag = new Dictionary<string, object>(
        propertyBag, StringComparer.OrdinalIgnoreCase);
    ConstructorInfo bestCtor = null;
    ParameterInfo[] bestParams = null;
    for (int i = 0; i < ctors.Length; i++)
    {
        ParameterInfo[] ctorParams = ctors[i].GetParameters();
        if (bestCtor == null || ctorParams.Length > bestParams.Length)
        {
            bestCtor = ctors[i];
            bestParams = ctorParams;
        }
    }
    if (bestCtor == null) throw new InvalidOperationException(
         "Cannot create - no constructor");
    object[] args = new object[bestParams.Length];
    for (int i = 0; i < bestParams.Length; i++)
    {
        args[i] = propertyBag[bestParams[i].Name];
        propertyBag.Remove(bestParams[i].Name);
    }
    object obj = bestCtor.Invoke(args);
    // TODO: if we wanted, we could apply any unused keys in propertyBag
    // at this point via properties
    return obj;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接