【问题标题】:Indexers and storage location索引器和存储位置
【发布时间】:2013-07-08 18:20:12
【问题描述】:

Indexer 的限制之一是 indexer 没有定义存储位置,因此 indexer 产生的值不能作为 ref 或 out 参数传递给方法。 我想知道我们为索引器定义的数组不是存储位置吗?

【问题讨论】:

  • 用户定义的索引器实际上是方法,因此您不能像传递字段那样将它们作为ref 传递是有道理的。您是否在询问将数组作为索引器包装的支持字段,或者尽管有上述规定,如何将Array 元素作为ref 传递?
  • 因为它实际上不是一个数组。它只是具有 1 个或多个参数的 getter 和 setter 方法的语法糖。您不能将myObject.GetItem(7) 作为ref 参数传递。
  • 谢谢大家,我想知道我们为索引器定义的数组不是存储位置吗?

标签: c# .net


【解决方案1】:

我将分解您问题的每个部分并尝试帮助您。

索引是否定义了存储中的位置?

索引器的一个限制是索引器没有定义存储位置

如果说“不能保证在该索引的类实现中抽象地定义存储中的某个位置”,那么是的,这是正确的,抽象地说,您是在索引器值的索引处定义一个值,但是这并不能保证您正在访问一个逻辑位置(在抽象级别,在低级别,所有东西都有一个位置)。基本上,索引是表示方法的一种很好的方式,该方法接受一个值并返回一个值,一个指示位置和使用括号语法的变量,以及确定调用哪个方法(获取或设置)的等号。我觉得我跑题了,但你可以在 on MSDN. 上查找更多关于索引实现的信息,但就像方法一样你必须让它有意义。这是一个失败的例子,并且在实现的后端没有实际位置。

一个奇怪的例子

public class MyClass
{

   private void Set(int i,string value)
   {
       Console.WriteLine("Your Index:{0}\r\nSet Value:{1}",i,value);
   }

   public string this[int i]
   {
        get
        {
            if(i<0)
               return "less than zero"; 
            if(i==0)
               return "This is zero";
            else if(i==1)
               return "This is one";
            else if(i==2)
               return "this is two";
            else
               return "more than two";

        }
        set
        {
           //value is a key word in a setter
           //representing the value on you are attempting to set
           Set(i,value);
        }
    }
}

不要这样做

我不确定为什么要这样做,但如果你确实想这样做,索引器只是表达一种方法的好方法,它可以作为索引,例如字典或列表,虽然有人可能会争辩说,从技术上讲,这个例子中的 getter 仍然没有意义,不应该使用索引来表达方法

你可以通过 ref 或 out 将索引传递给方法

因此,索引器生成的值不能作为 ref 或 out 参数传递给方法。

因为您通过索引访问的数据被封装在类中,除非该类公开了对该数据的引用,您无法访问它,因此您不能在方法调用中将其作为 ref 或 out 参数使用indexer 属性,所以我们需要查看访问 indexer 是否会暴露内存中的位置

简答

不,关键字`ref`和`out`基本上告诉IL使方法获取内存地址,`out`需要为内存中的位置分配一个新值,`ref`不需要更改但仍然允许它发生,因为 .NET 中并非所有语言都支持所有索引和属性,它们是通过将“get”和“set”中的指令更改为方法调用来实现的,“ref”和“out”需要在传递变量的内存,简化为 IL 尝试将索引器的 get/set 视为 out 变量等效于尝试将方法或新变量视为语义上无效的 `ref` 或 `out` 参数

长答案

你不能,原因是你在使用索引器时调用了一个方法,说你有这个作为你想要调用的方法


public void CreateNew(out object target)
{
    target = new object();
}

发生了什么

当你在某个级别调用 CreateNew 方法时的说明:

  1. 获取 CreateNew 指令位置
  2. 将传递给目标的变量的位置放入参数槽中
  3. 将位置中的内存值更改为堆中的某个位置 持有由“new object();”创建的对象声明
  4. 返回控制权

它不适用于索引器

分两种情况调用索引器

获取:

索引器“Get”方法出现在对象被索引并试图被访问的地方。发生这种情况时,将对某个方法进行方法调用,该方法表示您的 get 方法,该方法具有类似的签名

 
ValueType _get_index_IndexType_ValueType( IndexType index)

因此,如果编译器将您对 this 的调用解析为 out 参数,那么就像尝试将引用传递给尚未在内存中分配位置的变量一样。这就是为什么它不能与“Get”方法一起使用的原因,这是设计使然,因为从逻辑上讲,您无法从对象内存中的位置访问变量在内存中的位置。

设置:

索引器“Set”方法在对象被索引时出现在等号的左侧,在内部它被替换为代表您的具有这样签名的 set 方法的方法

 
void _set_index_IndexType_ValueType(IndexType index, ValueType Value)

因此,如果调用减少到此,则与尝试访问方法调用的内存中的位置相同,这不是我们想要的,我们要做的是在提供新的方法时调用 set 方法变量到索引,并在我们尝试访问它时获取。但是从设计上来说,这是不允许的,因为您可以自己轻松地做到这一点......

更多代码

如果这仍然没有意义,请尝试考虑下面的类,我们不使用索引器方法,而是使用带索引的 Get 和 Set



public class MyFooIndexableObject
{
    /* Note that "ValueType" and "IndexType" are 
     * just place holders for whatever type you
     * decide to make as your return type and 
     * index type respectively  
     *
     * Using a regular dictionary and an
     * extra variable to implement a default 
     * dictionary so it is not like the example 
     * is doing nothing.
     */
    private Dictionary _internalCollection;
    private readonly ValueType _defaultValue = new ValueType();
    public void FooSet(IndexType index, ValueType value)
    {
        if( index == null)
            //want to disallow index being null
            throw new NullArgumentException("index");

        if(_internalCollection==null)
            _internalCollection = new Dictionary();

        if ( value == null || value == _defaultValue )
           // want to remove it 
        {
            _internalCollection.Remove(index);
        }
        else
            _internalCollection[index]=value;
    }

    /* The Examples FooSet and FooGet 
     * would be similar method constructs to 
     * the ones made behind the scenes when 
     * you define the getter and setter for 
     * your indexed object 
     */

    public ValueType FooGet(IndexType index)
    {
        if( _internalCollection == null 
            || !_internalCollection.Contains(index) )
                return new _defaultValue;

        return _internalCollection[index];
    }

    public bool TryGetValueAtFirstNonDefault(out IndexType outIndex,  
                                              out ValueType outValue)
    {
        outParam = outIndex = null;

        if(_internalCollection!=null)
        {
            // no need to check we maintain this in the setter and getter
            var temp= _internalCollection.FirstOrDefault();
            if(temp!=null)
            {
                outParam = temp.Value;
                outIndex = temp.Key;
            }
        }
        return outParam != null;
    }

    private static void Swap( ref ValueType someRefParam, 
                       ref ValueType otherRefParam)
    {
        var temp = someRefParam;
        someRefParam = otherRefParam;
        otherRefParam = temp;
    }

    //use this instead
    public void SwapValueAtIndexes(IndexType index1, IndexType index2)
    {
        var temp = this.FooGet(index1);
        this.FooSet(index1, this.FooGet(index2) );
        this.FooSet(index2, temp);
    }

    public static void Main(string[] args)
    {
        var indexable = new MyFooIndexableObject();
        var index1 = new IndexType(0);
        var index2 = new IndexType(1);
        ValueType someValue;

        //do someValue = indexable[index1]
        someValue = indexable.FooGet(index1);

        //do indexable[index1] = new ValueType()
        indexable.FooSet(index1,new ValueType());

        //this does not make sense will not work
        //do Swap( out indexable[index1], out indexable[index2] )
        //just look how you would try to do this

        Swap( ref indexable.FooGet(index1), ref indexable.FooGet(index2));

        //Swap is looking for reference to a location in memory
        //but the method is returning the value of an object reference
        //which you can store in a variable with a location in memory
        //but has yet been assigned to one

        //Please note the whole idea of "location in memory" is abstract
        //it does not technically mean an actual location in physical 
        //memory but probably an abstraction handled by .NET,
        //don't try to hard to make sure you have the technical part 
        //100% correct, you are significantly detached from the metal
        //when coding at this level...the basic idea is the same
        //as physical memory locations on a machine

        //However, you can accomplish the same things that you would
        //want to accomplish with "out" and "ref" by creating methods
        //that take the indexed object and an index, such as the
        //SwapValueAtIndex method

        indexable.SwapValueAtIndex(index1,index2);

        //While precisely what SwapValueAtIndex does may
        //not translate to what Swap does logically
        //it is the same thing, which is good enough for us

    }
}

但你可以...

即使您无法获取对象的实际引用,您也可以将索引和索引对象传递给方法,这将有效地为您提供与变量引用相同的效果,因为您可以使用索引和它所在的对象


public void Swap(MyIndexedObject o, string indexer, object newValue, 
                    ref object oldValue)
{
    if(o.Contains(indexer))
    {
        oldValue = o[indexer];
    }
    else
        oldValue = null;

    o[indexer]=newValue;
}

public bool TryGetValue(MyIndexedObject o, string index, out object value)
{
    value=null;
    if(o.Contains(index))
    { 
        value = o[value];
        return true;
    }
    return false;
}

public void TrySwapValue(MyIndexedObject o, string indexer1, string indexer2)
{
    object valHolder1=null,valHolder2=null;
    if(TryGetValue(o,indexer1, out valHolder1))
    {
        Swap(o, indexer2, valHolder1,ref valHolder2);
        o[indexer1] = valHolder2;
    }
}

这是什么意思

如您所见,如果您有对象,则可以在逻辑上将索引用作位置(在索引对象实现有意义的情况下),这就是索引对象可以使用的地方

其他选项

如果你仍然想要一个索引对象的引用,你可以定义一个类,它有一个索引并获取和设置对象的值,在这个类中你可以包含类似历史的东西


public class MyObject : Dictionary{}

public class MyPlaceHolder
{
    public MyPlaceHolder(string index, MyObject target)
    {
        Index = index;
        TargetObject = target;
    }

    public string Index {get; private set;}
    public MyObject TargetObject {get; private set;}

    public object Value 
    {
        get
        {   
           return TargetObject[Index];
         }
        set
        {    
            var prev = TargetObject[Index];
            TargetObject[Index] = value;
            _prevVals.Push(prev);
        }
    }

    private Stack _prevVals = new Stack();

    public bool UndoSet()
    { 
        if(!_preVals.Count() == 0)
        {
            Value._prevVals.Pop();
            return true;
        }

        return false;
    }
}

是否存在索引的存储位置?

我想知道我们为索引器定义的数组不是存储位置吗?

是的,数组是一个位置,但索引定义不是该地址的直接反映。对象的索引是索引概念的抽象,它允许您根据传递给它的索引值访问对象,它不一定这样做,但它应该,从技术上讲它可能是一种与位置无关但不应该的方法。

但是对象不暴露下面的实际位置的方式是正确的,你使用封装来隐藏你的索引方法指定的位置的方式,这是我们有面向对象编程的原因之一 我不'不在乎 0 是否是实现级别的位置,只要在我使用它时有意义即可

使用索引的更好示例

我为只创建一个实际上很糟糕的索引对象示例而感到难过,希望没有人错误地认为这是一个好主意,因此将说明为什么隐藏位置是有意义的,这就是抽象背后的目的索引的

假设我想制作一个双键字典,我知道在我的代码的某些部分我将实现它,但我不知道如何实现,如果你有多个人在工作,所以你不想要人们在您编写类时等待,这样您就可以定义接口,并在其他程序员工作时实现它

public interface IMyDoubleStringDictionaryBase<T>
{
    T this[string index1, string value2]
    {
        get;set;
    }
}

第一次实现

你决定使用嵌套字典,这就是你想出的

public class MyDoubleStringDictionary<T> : IMyDoubleStringDictionaryBase<T>
{
    private Dictionary<string,Dictionary<string,T>> _baseCollection;

    public T this[string index1, string index2]
    {
        get
        {
            if(_baseCollection.ContainsKey(index1))
            {
                var nextDict = _baseCollection[index1];
                if(nextDict.ContainsKey(index2))
                {
                    return nextDict[index2];
                }
            }

            return default(T);
        }
        set
        {
            Dictionary<string,T> nextDict;
            if(_baseCollection.Contains(index1))
            {
                nextDict = _baseCollection[index1];
            }
            else
            {
                nextDict = new Dictionary<string,T>();
                _baseCollection.Add(index1,nextDict);
            }

            nextDict[index2] = value;
        }
    }
}

你有问题

由于某种原因,您的生产环境中无法使用 Dictionary 类,虽然这对您来说可能没有意义,但您被告知只使用 Array 数据结构,所有其他抽象数据结构需要您自己定义.您决定制作一个桶哈希,将密钥的两个哈希混合起来

public class MyNewDoubleStringDictionary<T> : IMyDoubleStringDictionaryBase<T>
{
    private class Node<T>
    {
        public Node<T> Next;
        public string Key1,Key2;
        public T Value;
    }

    private const int ARRAY_SIZE = 1024;
    private Node<T>[] _internalCollection = new Node<T>[ARRAY_SIZE];

    private int GetIndex(string key1, string key2)
    {
        const int key1mask = 0x0F0F0F0F;
        const int key2mask = 0xF0F0F0F0;
        var key1 = key1mask & key1.GetHashCode();
        var key2 = key2mask & key2.GetHashCode();
        var result = ((key1 | key2) & 0x7FFFFFFF)% ARRAY_SIZE;
        return result;
    }

    private Node<T> GetOrMakeNode(string key1,string key2)
    {
       int index = GetIndex(key1,key2);
       Node<T> currNode=_internalCollection[index];

       if(currNode == null)
       {
           _internalCollection[index] = currNode = new Node<T>();
        }
        else
       {
           while(!(currNode.Key1.Equals(key1)
                    &&currNode.Key2.Equals(key2))
               if(currNode.Next!=null)
               {
                  currNode = currNode.Next;
               }
               else
               {
                 currNode.Next = new Node<T>();
                 currNode = currNode.Next;
               }
       }
       if(currNode.Key1 == null || currNode.Key2 == null)
       {
           currNode.Key1 = key1;
           currNode.Key2 = key2;
       }
       return currNode;
    }

    public this[string index1, string index2]
    {
        get
        {
           var node = GetOrMakeNode(index1,index2);
           return node.Value;
        }
        set
        {
           var node = GetOrMakeNode(index1,index2);
           node.Value = value;
        }
    }
}

结果

即使您在需求和实现方面发生了变化,它也不会中断您团队的任何工作,因为您没有引用对象的内部工作,因此它不可能搞砸他们的工作。

为什么有意义

您不关心位置在哪里,如果实际实现正在查看某个位置,您也不应该真正担心,只要知道您必须以某种方式连接索引并且您将能够使用它

【讨论】:

  • 哇...史诗...我认为令人困惑的一件事是,在您的“但您可以...”部分中,您传递了MyIndexedObject o 参数。然后调用 o.Contains,这意味着任何 Indexed 对象都有一个不正确的 Contains 方法。
  • 我不是这么说,我只是在暗示这个特定的对象确实有一个Contains 方法,所以除了索引捕获的对象之外我还在做其他事情
  • 哇,答案真棒,值得奖励!
  • @FelixK。我想我做得太久了,无法修复它。在一个地方修复一些东西,忘记在另一个地方修复它,比如使用错误的术语或其他什么......我真的很喜欢这种类型的东西,所以我什至没有注意到它直到我完成了多长时间
【解决方案2】:

http://msdn.microsoft.com/en-us/library/vstudio/6x16t2tx.aspx

Indexer 只是特殊的 getter 和 setter。并且 ref 或 out 始终只是局部变量。索引器甚至不必指向存储位置,而是可以返回计算值。

索引器甚至不必用于数组。例如,在矢量图像中,我可以定义索引器 myvectorimage[x][y] 以便它返回 x 和 y 位置的颜色,但数据永远不会以这种方式存储。

【讨论】:

  • 只是一个小提示:refout 也可以用于除局部变量之外的字段。
猜你喜欢
  • 1970-01-01
  • 2012-08-24
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多