是否有任何托管方式在结构中使用变量的指针?

时间:2014-10-29 08:37:03

标签: c# pointers structure managed

我正在尝试编写一个能够解析xml文件的通用函数

这是代码

public struct XmlArg
{
    public string Name;
    public Type T;
    public object Value;
};

static bool ParseXmlArgs(XmlReader xml, params XmlArg[] args)
{
    for (int i = 0; i < args.Length; ++i)
    {
        if (xml.MoveToContent() != XmlNodeType.Element || xml.Name != args[i].Name)
        {
            return false;
        }
        args[i].Value = xml.ReadElementContentAs(args[i].T, null);
    }
    return true;
}

static void Main(string[] args)
{
    int a = 0;

    ParseXmlArgs(
        XmlTextReader.Create("C:\\Users\\Yazilim\\Desktop\\XML.xml"),
        new XmlArg[]{
            new XmlArg() { Name = "ErrorCode", T = typeof(int), Value = a}});
}

我知道我应该传递一个指向Value的指针(它的类型应该是除了对象之外的其他类型)

但我不希望它是非托管方式。

是否有任何管理方式在结构中使用变量的指针?

(该功能可能错误或不正确,而且不是重点)

2 个答案:

答案 0 :(得分:1)

简答:创建一个数组,将其传递给方法,然后读取其内容:

您需要在调用方法之前构造XmlArg[]数组,并在解析完成之前忘记a变量:

var reader = XmlTextReader.Create("C:\\Users\\Yazilim\\Desktop\\XML.xml");
var results = new []
{
    new XmlArg() { Name = "ErrorCode", T = typeof(int) },
    new XmlArg() { Name = "ErrorMessage", T = typeof(string) }
};

if (ParseXmlArgs(reader, results) == true)
{
    // results were passed by reference, so do something with these values
}

答案很长:

引用类型的变量(在.NET中是任何class)包含对其数据的引用,其中数据在内存中的不同位置,以及有关类型的其他信息。与此相反,值类型的变量(如intfloat或任何struct)包含实际值 - 所有数据都包含在该变量中,并且不存在其他元数据。

当您pass a value type variable作为参数值(这是在.NET中传递参数的默认方式)到方法时,会生成该值的浅表副本。这意味着所有数据,无论您的struct有多大(在这种情况下,它只是int)都会被复制,并且方法中的值发生的任何内容都不会有对初始值有任何影响。另一方面,如果您pass a reference type variable按方法的值,该方法将获取引用(实际上是指针)的副本,但引用指向的原始数据不会被复制,这意味着方法中复制的引用仍然指向唯一的对象实例,并且该方法对数据执行的任何操作都会反映在方法之外。

Using the ref parameter,您可以通过引用使方法接受其参数。通过引用传递值类型将创建对原始值的引用,而不复制数据,类似于按值传递引用类型时发生的情况。在这种情况下,修改方法内部的引用值会修改方法外部变量中的原始值,因为不会生成原始数据的副本。如果您使用ref关键字通过引用传递引用类型,那么您实际上是将引用传递给实际数据的引用,这意味着该方法现在有机会甚至使原始变量指向一个完全不同的对象(除了修改原始对象。

此外,将值类型转换为object(当您将其分配给XmlArg.Value属性时会发生),boxes the value。再次装箱值类型会在内存中的其他位置创建值的副本,并创建指向该副本的引用类型(object)。同样,原始值的“链接”也会丢失,这意味着从该点开始对该对象所做的任何事情都不可能对原始变量产生任何影响。

所以,在你的情况下,你基本上有:

// a value type variable
int a = 0; 

// implicit boxing occurs, creating a copy of the value
// ("link" to a is lost)
object boxed_a = (object)a;

// a value type (struct) variable which contains a member which points
// to the boxed int value 0
XmlArg arg = new XmlArg() { Value = boxed_a };

// here, you are creating an array of XmlArg, but since XmlArg is a struct, 
// the array actually contains a *copy* of the `arg` value
// ("link" to arg is also lost)
XmlArg[] args = new [] { arg };

在这种情况下可能有用的一件事是variable closure in lambdas。本质上,在匿名方法中使用变量会创建对原始值的实际引用,这允许您从任何地方修改它 - 但真正的原因是编译器实际上将原始值作为字段放置在为其创建的匿名类中lambda:基本上,它不再是一个普通的旧局部变量,这允许lambda处理原始数据。

在你的情况下,你会做类似的事情:

// define a common interface (Value is an object)
interface IXmlArg
{
    string Name { get; }
    Type Type { get; }
    object Value { get; set; }
}

// I used generics to get type safety and to get type T information "for free"
class XmlArg<T> : IXmlArg
{
    public XmlArg(string name, Action<T> setter, Func<T> getter)
    {
        _name = name;
        _setter = setter;
        _getter = getter;
    }

    private readonly string _name;
    public string Name { get { return _name; } }

    public Type Type { get { return typeof(T); } }

    private readonly Func<T> _getter;
    private readonly Action<T> _setter;
    public object Value
    {
        get { return (object)_getter(); }
        set { _setter((T)value); }
    }
}

在你的主要方法中,你会这样做:

// local value type variable
int a = 0;

// we are creating two lambdas, one to set the value to a, and one to read it
IXmlArg arg = new XmlArg<int>("ErrorCode", x => a = x, () => a);

// this method will use the `Value` setter, which in turns calls the lambda
ParseXmlArgs(reader, new [] { arg });

答案 1 :(得分:0)

请注意,您的结构XmlArg是一个可变值类型,被许多人视为“邪恶”。

也许您希望Value成员是对具有可以更改的整数的类型的引用?如果你写:

public class ChangeableInt32    // 'class' gives a reference type
{
    public int Content;
}

public struct XmlArg
{
    public string Name;
    public Type T;
    public ChangeableInt32 Value;
}

然后:

        args[i].Value.Content = /* read the int */;