locked
How to use certain properties of a type parameter in a generic method RRS feed

  • Question

  • I am trying to write a generic method in which one parameter can be either a byte[] or a string.  Inside the method I want to use the Length property which is present for a byte[] or string as well as an indexer which also works for byte[] and string.  However, I don't know how to get around the compiler message 'T does not contain a definition for Length...' and 'Cannot apply indexing with [] to an expression of type T'.  A code example is shown below.  Please help.
            static void DisplayItems<T> (T obj, int itemsPerRow)
            {
                int index = 0;
                int nextItem;
                
                while (index < obj.Length)
                {
                    for (int itemsInRow = 0; itemsInRow < itemsPerRow; itemsInRow++)
                    {
                        nextItem = index + itemsInRow;
    
                        if (nextItem < obj.Length)
                        {
                            Write (obj[nextItem]);
                        }
                        else
                            break;
                    }
    
                    WriteLine ();
    
                    index += itemsPerRow;
                }
            }
    


    MesPia

    Wednesday, November 18, 2020 6:18 PM

Answers

  • This problem can be solved without generics too:

    static void DisplayItems( dynamic obj, int itemsPerRow )
    {
    	int index = 0;
    	int nextItem;
    
    	while( index < obj.Length )
    	{
    		for( int itemsInRow = 0; itemsInRow < itemsPerRow; itemsInRow++ )
    		{
    			nextItem = index + itemsInRow;
    
    			if( nextItem < obj.Length )
    			{
    				Write( obj[nextItem] );
    			}
    			else
    				break;
    		}
    
    		WriteLine( );
    
    		index += itemsPerRow;
    	}
    }
    
    static void Write( byte b )
    {
    	// . . .
    }
    
    static void Write( char b )
    {
    	// . . .
    }
    

    The solution with generics will be probably more complex.

    • Marked as answer by MesPia Wednesday, November 18, 2020 8:22 PM
    Wednesday, November 18, 2020 7:16 PM
  • You cannot access members of an arbitrary type X unless T is restricted to X. Otherwise T must be limited to `object` so you are stuck with the base type members only. However there are several alternatives.

    1) You don't need a generic parameter if your code only works with byte[] and string. Just define overloads that support them. If you need to support other types that might have `Length` property then create an interface to wrap the member(s) and provide an overload that works for them as well. Unfortunately generic constraints do not allow arrays at this time otherwise that would be the preferred approach.

    2) For this very specific example make T have a generic constraint of IEnumerable<T> and then either arrays or strings work with it. Then use the `Count` method instead. In fact this is the way I'd probably go as what you're trying to do in your generic method is really just a combination of Skip/Take calls that IEnumerable<T> already supports.

    static void DisplayItems<T> ( IEnumerable<T> obj, int itemsPerRow )
    {            
        var length = obj.Count();
        var read = 0;
        while (read < length)
        {
            var items = obj.Skip(read).Take(itemsPerRow);
            foreach (var item in items)
                Console.Write(item);
    
            Console.WriteLine();
            read += items.Count();
        }
    }

    Could probably optimize this call even more.

    3) Use a delegate passed as a parameter to get the length of the items, or accept it as a parameter with overloads for arrays and strings.



    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by MesPia Wednesday, November 18, 2020 8:42 PM
    Wednesday, November 18, 2020 7:46 PM

All replies

  • This problem can be solved without generics too:

    static void DisplayItems( dynamic obj, int itemsPerRow )
    {
    	int index = 0;
    	int nextItem;
    
    	while( index < obj.Length )
    	{
    		for( int itemsInRow = 0; itemsInRow < itemsPerRow; itemsInRow++ )
    		{
    			nextItem = index + itemsInRow;
    
    			if( nextItem < obj.Length )
    			{
    				Write( obj[nextItem] );
    			}
    			else
    				break;
    		}
    
    		WriteLine( );
    
    		index += itemsPerRow;
    	}
    }
    
    static void Write( byte b )
    {
    	// . . .
    }
    
    static void Write( char b )
    {
    	// . . .
    }
    

    The solution with generics will be probably more complex.

    • Marked as answer by MesPia Wednesday, November 18, 2020 8:22 PM
    Wednesday, November 18, 2020 7:16 PM
  • You cannot access members of an arbitrary type X unless T is restricted to X. Otherwise T must be limited to `object` so you are stuck with the base type members only. However there are several alternatives.

    1) You don't need a generic parameter if your code only works with byte[] and string. Just define overloads that support them. If you need to support other types that might have `Length` property then create an interface to wrap the member(s) and provide an overload that works for them as well. Unfortunately generic constraints do not allow arrays at this time otherwise that would be the preferred approach.

    2) For this very specific example make T have a generic constraint of IEnumerable<T> and then either arrays or strings work with it. Then use the `Count` method instead. In fact this is the way I'd probably go as what you're trying to do in your generic method is really just a combination of Skip/Take calls that IEnumerable<T> already supports.

    static void DisplayItems<T> ( IEnumerable<T> obj, int itemsPerRow )
    {            
        var length = obj.Count();
        var read = 0;
        while (read < length)
        {
            var items = obj.Skip(read).Take(itemsPerRow);
            foreach (var item in items)
                Console.Write(item);
    
            Console.WriteLine();
            read += items.Count();
        }
    }

    Could probably optimize this call even more.

    3) Use a delegate passed as a parameter to get the length of the items, or accept it as a parameter with overloads for arrays and strings.



    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by MesPia Wednesday, November 18, 2020 8:42 PM
    Wednesday, November 18, 2020 7:46 PM
  • Thanks to both you.  Both are excellent suggestions.  I had forgotten about the 'dynamic' keyword.  That's a very good idea.

    MesPia

    Wednesday, November 18, 2020 8:23 PM
  • Both answers were excellent suggestions.  Since I could only mark one reply as answer, I gave it to Viorel.  I had forgot about the possibility of using 'dynamic' to determine the type at runtime.

    Thanks.


    MesPia

    Wednesday, November 18, 2020 8:25 PM
  • FYI, here is the modified complete pair of methods that are now working.

            /// <summary>
            /// Show how to display a binary object as a string
            /// using two formats.
            /// </summary>
            static void ConvertBinaryObjectToString ()
            {
                byte[] binaryObject = new byte[128];
    
                // Populate array with random bytes
                (new Random ()).NextBytes (binaryObject);
                WriteLine ("Binary Object as Bytes");
    
                // Convert to Base64 string and output as text
                string encoded = Convert.ToBase64String (binaryObject);
    
                WriteLine ("\nBinary Object as Base64");
                DisplayItems (encoded, 50);
            } // method ConvertBinaryObjectToString
    
            /// <summary>
            /// Helper method for ConvertBinaryObjectToString() used
            /// to display the byte[] items or string characters.
            /// </summary>
            /// <param name="obj">The byte[] or string to be displayed</param>
            /// <param name="itemsPerRow">Number of items to display per row</param>
            static void DisplayItems (dynamic obj, int itemsPerRow)
            {
                int index = 0;
                int nextItem;
    
                while (index < obj.Length)
                {
                    for (int itemsInRow = 0; itemsInRow < itemsPerRow; itemsInRow++)
                    {
                        nextItem = index + itemsInRow;
    
                        if (nextItem < obj.Length)
                        {
                            if (obj.GetType () == typeof (byte[]))
                                Write ($"{obj [nextItem]:X2} ");
                            else
                                Write (obj [nextItem]);
                        }
                        else
                            break;
                    }
    
                    WriteLine ();
    
                    index += itemsPerRow;
                }
            }
    


    MesPia

    Wednesday, November 18, 2020 8:40 PM
  • I think that you can mark more than one answer.

    Wednesday, November 18, 2020 8:40 PM
  • You're right.  I marked both answers now.  I think that maybe it was in the Microsoft Community Forum that only one answer could be marked.

    MesPia

    Wednesday, November 18, 2020 8:43 PM