1

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

4

2 回答 2

3

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

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

Indexers 的限制之一是 indexer 没有定义存储位置

如果说“不能保证在该索引的类实现中抽象地定义存储中的某个位置”,那么是的,那是正确的,抽象地说,您是在索引器值的索引处定义一个值,但这并不保证您正在访问一个逻辑位置(在抽象级别,在低级别,所有东西都有一个位置)。基本上,索引是表示方法的一种很好的方式,该方法接受一个值并返回一个值,一个指示位置和使用括号语法的变量,以及确定调用哪个方法(获取或设置)的等号。我觉得我跑题了,但您可以在 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 参数传递,所以我们需要看看访问索引器是否暴露了内存中的一个位置

简答

不,关键字`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;
        }
    }
}

结果

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

为什么有意义

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

于 2013-07-08T21:18:47.297 回答
2

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

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

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

于 2013-07-08T18:40:19.857 回答