Pergunta

Currently, I have this code:

class SO
{
    public SO()
    {
        var ptrManager = new PointerManager();
        int i = 1;

        ptrManager.SavePointer(ref i);

        Console.WriteLine(i); // prints 1

        i = 2; //change value to 2 within the current scope
        Console.WriteLine(i); // prints 2

        ptrManager.SetValue(3); // change value to 3 in another (unsafe) scope
        Console.WriteLine(i); // prints 3
    }
}

unsafe class PointerManager
{
    private int* ptr;
    public void SavePointer(ref int i)
    {
        fixed (int* p = &i)
        {
            ptr = p;
        }
    }

    public void SetValue(int i)
    {
        *ptr = i;
    }
}

First, I tell the PointerManager to create a pointer pointing to my int i. This way, later, i'll be able to tell the PointerManager to change the value of i, without having to pass i in again.

This seems to work well so far. However, I've read that if the GC decides to move i around in memory, then my pointer ptr will become invalid, and subsequent calls to SetValue will have unpredictable behaviour.

So, how do I prevent GC from moving i around? Or, alternatively, is there a better way of accomplishing my goal?

I've also been told that simply adding GCHandle.Alloc(i, GCHandleType.Pinned); will work, but that doesn't seem right...

Edit: To be more specific, GCHandle.Alloc would be called at the beginning of the SavePointer method.

Foi útil?

Solução

Generally you do not want to prevent the GC from moving around anything - especially in your case (transactional objects), where I would rather consider options like:

  • making objects savable+restorable=serializable
  • doing it with some sort of manager (maybe this fits well to the idea you had with pointers) that you can pass closures!

Second option explained:

class VariableManager
{
    private Action<int> setter;
    public void VariableManager(Action<int> setter)
    {
        this.setter = setter;
    }

    public void SetValue(int i)
    {
        setter(i);
    }
}

Simply create the manager via new VariableManager(i => yourInt = i) and viola - there you have your desired semantics without pointers!

Outras dicas

You can use the power of Function and Action types to achieve the same without any unsafe code. Try to avoid any unsafe ocde or pointers, if you aren't interfacing with non-managed code, you don't need them:

using System;

namespace ConsoleApplication16
{
  class SO
  {
    public static void Main()
    {
      var manager = new LateSetter<int>();

      int i = 1;

      manager.SaveSetter(newValue => i = newValue);

      Console.WriteLine(i); // prints 1

      i = 2;
      Console.WriteLine(i); // prints 2

      manager.SetValue(3); // change value to 3
      Console.WriteLine(i); // prints 3

      Console.ReadLine();
    }
  }

  class LateSetter<T>
  {
    private Action<T> set;

    public void SaveSetter(Action<T> setter)
    {
      this.set = setter;
    }

    public void SetValue(T i)
    {
      this.set(i);
    }
  }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top