Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do Generic Methods Know About Inaccessible Types? (Or "How I Lost a Dollar")

So, we had a little bet on our team whether something would work or not. I lost. Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using MyNamespace;

namespace PrivateGeneric
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void WhoIsRight_Click(object sender, RoutedEventArgs e)
        {
            var store = new GenericDataStore();

            try
            {
                var data = new MyPrivateClass();
                store.StoreTheData(data);
                object theData = store.GetTheData<MyPrivateClass>();
                if (theData == null || !(theData is MyPrivateClass))
                {
                    throw new Exception();
                }
                MessageBox.Show("Seann was right.");
            }
            catch (Exception)
            {
                MessageBox.Show("WOOOOOOOOOT!!!!! PHIL WINS!!!!!! HAHAHAHA!!!!!! PWNED!!!!!!!");
            }
        }

        private class MyPrivateClass
        {

        }
    }
}

namespace MyNamespace
{
    public class GenericDataStore
    {
        readonly List<object> _store = new List<object>();

        public void StoreTheData<T>(T data)
        {
            _store.Add(data);
        }

        public object GetTheData<T>()
        {
            //How does "t is T" work if T is a private type unknown to this class?
            return _store.FirstOrDefault(t => (t is T));
        }
    }
}

The question of why it works is highlighted in the code. Does "is" not need to cast to T to determine whether it is in fact a T? And does it not need the type to be accessible to do so? Obviously that's not the case, so what mechanism does the highlighted line use to make its determination?

like image 890
Phil Sandler Avatar asked Dec 07 '22 22:12

Phil Sandler


2 Answers

Think about it this way:

class C
{
    private const int BigSecret = 0x0BADFOOD;
    public static void M() { D.X(BigSecret); }
}
class D
{
    public static void X(int y) { Console.WriteLine(y); }
}

When C.M is called, D.X learns the secret and shares it with the world. The fact that BigSecret is private is irrelevant; you passed its value along. Code is not disallowed from seeing the value of a private field, it is disallowed from using the name of the private field. X is perfectly free to use the value of y as it sees fit; it does not do anything illegal, like trying to use the name of BigSecret.

Type arguments are the same way. They're logically arguments passed to the generic function. Of course they are not passed using the same mechanisms under the hood, but logically they're just arguments passed around that set the values of a type parameter, just like regular arguments set the values of formal parameters. If you don't want to pass around a secret private type then don't pass it as a type argument.

In your case StoreTheData is not allowed to use the name of PrivateClass, but if passed the value of PrivateClass, it can use it all it wants. If you didn't want it used then you shouldn't have passed it around. Same as if you didn't want BigSecret known, then you shouldn't have passed it around. A secret shared is no longer a secret.

Incidentally, we take advantage of the fact that a generic type does not do an accessibility check on its type parameters to make anonymous types work. See my article on the subject for details:

http://blogs.msdn.com/b/ericlippert/archive/2010/12/20/why-are-anonymous-types-generic.aspx

like image 161
Eric Lippert Avatar answered May 19 '23 20:05

Eric Lippert


I think you're confusing two separate concepts -- visibility and runtime type information.

Every struct/class has type information. How you get that type information isn't important. E.g. you could reflect over an assembly and dig out some private type and the GetTheData<T> method would still function as expected.

In this case, the MyPrivateClass type that you pass in is visible at the call site (as MyPrivateClass is nested inside your main class, and is thus visible to it). As such, you know the type at the call site and you pass it into the GetTheData<T> method.

At that point, the type's information is known and is passed through as the generic type parameter. Your code then uses that generic type parameter to do the type check.

Do this:

GetTheData<T>()
{
    Console.WriteLine("Type information: {0}", typeof(T).FullName); 
}

If you call this method with the type MyPrivateClass -- it will print the type's name as expected.

*edited -- removed the stuff about Type comparisons as Eric L pointed out that there's an IL instruction for the is/as operators, and I'm not sure what happens beyond that point. For the purposes of the question, all you need to know is that the runtime knows about types and can compare them happily enough.

like image 22
Mark Simpson Avatar answered May 19 '23 20:05

Mark Simpson