接口不是通过引用传递的

本文关键字:引用 接口 | 更新日期: 2023-09-27 17:50:33

我需要在运行时更改接口的实现。这个接口被许多类引用。

这是我的测试用例,它没有像我预期的那样工作。(改变接口的引用似乎不会在所有使用它的地方更新)

示例如下:

// interface to change at runtime
interface IDiet
{
    void Eat();
}
class CarnivoreDiet : IDiet
{
    public void Eat()
    {
        Debug.WriteLine("Eat chicken");
    }
}
class HerbivoreDiet : IDiet
{
    public void Eat()
    {
        Debug.WriteLine("Eat spinach");
    }
}
class Human : IDiet
{
    private readonly IDiet _diet;
    public Human(IDiet diet)
    {
        _diet = diet;
    }
    public void Eat()
    {
        _diet.Eat();
    }
}
void Main()
{
    IDiet diet = new CarnivoreDiet();
    Human human = new Human(diet);
    human.Eat();
    // outputs "Eat chicken"
    diet = new HerbivoreDiet();
    human.Eat();
    // still outputs "Eat chicken" even if i changed the reference of IDiet interface
}

为什么在Human实例中IDiet接口没有更新?

PS: IDiet接口在很多类中使用,所以增加SetDiet(IDiet diet)这样的方法是不能解决的。

接口不是通过引用传递的

当您通过以下代码传递对象引用时:

Human human = new Human(diet);

引用(对象的地址)被复制到它的形参:

public Human(IDiet diet)
{
        _diet = diet;
}

它们是3块不同的内存,包含对对象的相同引用:您的原始diet,参数变量(diet)和类属性(_diet)。

所以当你执行你的代码时:

diet = new HerbivoreDiet();

这个内存块现在包含对新对象(HerbivoreDiet)的引用,但是Human内部的内存块仍然引用旧对象。

既然您已经实现了一个facade,为什么不实现第二个呢:

class ChangableDiet : IDiet
{
    private IDiet _diet;
    public ChangableDiet (IDiet diet)
    {
        _diet = diet;
    }
    public Diet Diet { get { return _diet;} set { _diet = value; } }
    public void Eat()
    {
        _diet.Eat();
    }
}

构造其中一个,并将it传递给Human构造函数。

void Main()
{
    IDiet diet = new CarnivoreDiet();
    var changable = new ChangableDiet(diet)
    Human human = new Human(changable );
    human.Eat();
    // outputs "Eat chicken"
    changable.Diet = new HerbivoreDiet();
    human.Eat();
    // outputs "Eat spinach"
}

diet是一个局部变量,它的值是指向内存中实际对象的指针(地址)。

如果你去diet.SomeProperty = "foo",你改变了内存中的对象,这是处处反映。

如果输入diet = new Diet(),则用指向另一个对象的新指针覆盖局部变量diet

Human引用没有改变,因为指针是按值传递的。它们指向的对象是相同的(除非您覆盖diet),但指针是彼此的副本。

你应该将diet设置为一个可访问的非只读属性,并对其进行修改。或者创造一个新的人

问题:为什么IDiet接口不更新Human实例内?

因为你的接口引用是通过value传递的,这意味着你在Human类中持有的引用是指向同一个IDiet 的副本

之后做

diet = new HerbivoreDiet();

您只需更改Human类之外的引用,该类仍然保留自己的副本,因此没有机会更改其引用。

这是一个你可以实现的解决方案:

public interface IProvider<T>
{
  public T Current {get; set;}
}
public class DietProvider : IProvider<IDiet>
{
  public IDiet Current {get; set;}
}
class Human : IDiet
{
  private readonly IProvider<IDiet> _dietProvider;
  public Human(IProvider<IDiet> dietProvider)
  {
    _dietProvider = dietProvider;
  }
  public void Eat()
  {
    _dietProvider.Current.Eat();
  }
}
void Main()
{
  IProvider<IDiet> dietProvider= new DietProvider { Current = new CarnivoreDiet()};
  Human human = new Human(dietProvider);
  human.Eat();
  // outputs "Eat chicken"
  dietProvider.Current = new HerbivoreDiet();
  human.Eat();
}

这与接口无关。您的代码所做的是按值将变量传递给类构造函数,尽管该值是对实例的引用。然后将该值复制到新创建对象实例的成员中。然后,当您更改最初用于保存该值的变量中的值时,它不会对类实例中的内部成员产生影响。

它将与 ints完全相同。考虑以下示例:

class MyClass {
   private int mInt;
   public MyClass(int theInt) {
      mInt = theInt;
   }
   public int GetTheInt() { return mInt; }
}
void Main()
{
   int anInt = 5;
   MyClass MyInstance(anInt);
   anInt = 10;
   if(MyInstance.GetTheInt() == anInt)
      // Will never happen
   ;
}