索引器和存储位置

时间:2013-07-08 18:20:13

标签: c# .net

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

2 个答案:

答案 0 :(得分:3)

我将打破你问题的每一部分并试着帮助你。

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

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

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

简答

不,关键词`ref`和`out`基本上告诉IL让方法占用一个内存地址,`out`要求内存中的位置被分配一个新值,`ref`不需要改变但是仍然允许它发生,因为.NET中的所有语言都不支持所有索引和属性,它们是通过更改&#34; get&#34;中的指令来实现的。和&#34;设置&#34;在方法调用中,`ref`和`out`需要在内存中传递变量的位置,减少到IL试图处理索引器的get / set,因为out变量等同于尝试处理方法或新变量作为语法无效的`ref`或`out`参数

长答案

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


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

发生了什么

当你在某个级别的指令上调用CreateNew方法时:

        
  1. 采取CreateNew指令位置
  2.     
  3. 将传递给目标的变量的位置放入参数槽
  4.     
  5. 将位置中的内存值更改为堆中的位置         持有&#34; new object()创建的对象;&#34;声明
  6.     
  7. 返回控件
  8. 它不适用于索引器

    在两种情况下调用索引器

    获取:

    索引器&#34;获取&#34;方法出现在对象被索引并尝试访问的位置。发生这种情况时,会对某些方法进行方法调用,该方法表示具有类似

    的签名的get方法
     
    ValueType _get_index_IndexType_ValueType( IndexType index)
    

    因此,如果编译器将您对此的调用解析为out参数,那么就像尝试将引用传递给尚未在内存中分配了位置的变量。这就是为什么它不能与&#34; Get&#34;方法,这是通过设计完成的,逻辑上你无法从内存中某个对象的内存中访问一个变量。

    设置:

    索引器&#34; Set&#34;当对象被索引并且在等号的左侧时,会出现方法,在内部,它会替换为表示具有此类签名的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是实现级别的位置,只要在我使用它时有意义

    使用索引的更好示例

    我感觉不好只创建一个实际上非常糟糕的Indexed对象的例子,并且希望没有人错误地认为这是一个好主意,所以将说明为什么隐藏位置是有意义的,这是抽象背后的目的索引

    让我们说我想制作一个双键字典,我知道在我的代码的某些部分我将实现它,但我不知道如果你有多个人在工作因此,您不希望人们在编写类时等待,因此您可以定义界面,并在其他程序员工作时实现它

    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;
            }
        }
    }
    

    结果

    即使你的要求和实施有所改变,它也不会打断你团队的任何工作,因为你没有引用对象的内部工作,所以它不可能弄乱他们的工作。

    为什么它有意义

    你不关心这个位置的位置,如果实际的实现正在查看某个位置,你应该真的担心,只要知道你必须以某种方式连接索引,你就会能够使用它

答案 1 :(得分:2)

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

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

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