C
C#4w ago
Faker

✅ Nested classes in C# with parent being a generic class

Hello guys, consider the code in the picture, can someone explain why the type parameters declared in our parent generic class is available in the nested private class please. Also notice in the following code:
C#
// Type parameter T in angle brackets.
public class GenericList<T>
{
// The nested class is also generic, and
// holds a data item of type T.
private class Node(T t)
{
// T as property type.
public T Data { get; set; } = t;

public Node? Next { get; set; }
}

// First item in the linked list
private Node? head;

// T as parameter type.
public void AddHead(T t)
{
Node n = new(t);
n.Next = head;
head = n;
}

// T in method return type.
public IEnumerator<T> GetEnumerator()
{
Node? current = head;

while (current is not null)
{
yield return current.Data;
current = current.Next;
}
}
}
C#
// Type parameter T in angle brackets.
public class GenericList<T>
{
// The nested class is also generic, and
// holds a data item of type T.
private class Node(T t)
{
// T as property type.
public T Data { get; set; } = t;

public Node? Next { get; set; }
}

// First item in the linked list
private Node? head;

// T as parameter type.
public void AddHead(T t)
{
Node n = new(t);
n.Next = head;
head = n;
}

// T in method return type.
public IEnumerator<T> GetEnumerator()
{
Node? current = head;

while (current is not null)
{
yield return current.Data;
current = current.Next;
}
}
}
We declare a private class with parentheses, what does that implies please.
No description
62 Replies
Angius
Angius4w ago
Type parameters of the parent are available to the children The same way
class Foo<T>
{
T Bar(T thing) => thing;
}
class Foo<T>
{
T Bar(T thing) => thing;
}
is possible without giving Bar a type parameter, so is
class Foo<T>
{
class Bar
{
public T Thing { get; set; }
}
}
class Foo<T>
{
class Bar
{
public T Thing { get; set; }
}
}
333fred
333fred4w ago
In addition, the parenthesis are unrelated to type parameters, that's a primary constructor
333fred
333fred4w ago
Declare and use C# primary constructors in classes and structs
Learn how and when to declare primary constructors in your class and struct types. Primary constructors provide concise syntax to declare constructor parameters available anywhere in your type.
Faker
FakerOP4w ago
Hmm I think I still have some confusion on generic, can you just confirm the following pls: Here we declare a generic class. We are using a function name Bar with parameter T (T is same type as in class definition) and that function is returning thing which has the same data type as the class Foo, that is T. Now consider second piece of code. I understood that Bar is kind of "inheriting" the type of Foo, that is T. Does that mean T becomes a field of Bar? or we can just use it as a property? Also, is there a reason why we would prefer to use the idea of nested classes? ahh didn't know about that, I just had a look, from what I've understand, it's just a shorter way to define constructors?
333fred
333fred4w ago
Effectively And it answers the second part of your question there
Faker
FakerOP4w ago
hmm the idea of why we use nested classes?
333fred
333fred4w ago
No, the stuff about what does T become
Faker
FakerOP4w ago
I was just reading a bit, so, from what I've understand, when we define the generic type parameter in the outer class definition, the inner class, private is inheriting that field also, so we do have a data type Thing here for e.g. Now the "Thing" variable, is it available for both the class Foo and Bar or only for Bar? yeah I see, in fact, T does become a field or Bar, right ?
333fred
333fred4w ago
T becomes nothing. It's just a type parameter t (note the lowercase) is a field of Bar, of type T
Faker
FakerOP4w ago
yeah sorry, I mean t, yep, so t is the field of both Bar and Foo or only that of Bar ?
333fred
333fred4w ago
t is a primary constructor parameter of Bar
Faker
FakerOP4w ago
ah I see, is it correct to say the following: in the above code, the outer class is a generic class with type parameter T. Normally, this mean this class can have a single instance variable of type T. Now, when we define the inner private class, that inner private class kind of inherit the T generic parameter from the outer class meaning that we can also define an instance variable with type T. In this case, this is represented by small letter t
333fred
333fred4w ago
Normally, this mean this class can have a single instance variable of type T.
Not sure where you got this impression, but no
MODiX
MODiX4w ago
333fred
REPL Result: Success
record class C<T>
{
public T T1;
public T T2;
}

var c = new C<string>() { T1 = "Hello", T2 = "World" };
Console.WriteLine(c);
record class C<T>
{
public T T1;
public T T2;
}

var c = new C<string>() { T1 = "Hello", T2 = "World" };
Console.WriteLine(c);
Console Output
C { T1 = Hello, T2 = World }
C { T1 = Hello, T2 = World }
Compile: 503.698ms | Execution: 68.940ms | React with ❌ to remove this embed.
Faker
FakerOP4w ago
ah ok, we can define as many instanve variable of type T as we want?
333fred
333fred4w ago
Within some limit set by the runtime, but effectively yes If you need more than it allows, you need to ask yourself what the hell you're doing 😄
Faker
FakerOP4w ago
yep I see beside that, was there any missconception of what I said afterwards pls
333fred
333fred4w ago
You're pretty close. I could make some quibbles about calling t an "instance variable", but that's to do with primary constructors, not with generics
Faker
FakerOP4w ago
give your feedback !! else I would do the same error next time :c
333fred
333fred4w ago
As I said earlier, t isn't an instance variable. It's a primary constructor parameter
Faker
FakerOP4w ago
ah but normally, when we pass t in the primary constructor, basically it should have a field, no? so if for example in a class of Person, I use string name in a primary constructor, this mean behind the scene I have a field of name ?
333fred
333fred4w ago
No, it doesn't mean that You may get a backing field if you actually capture the parameter But in your example, t is not captured, so there is no field backing t. Instead, your property Data has a backing field
Faker
FakerOP4w ago
ahhhh I see this is what you mean by captured, like we are creating a property ok ok, starts to make sense, so t is a primary constructor noted yep
333fred
333fred4w ago
private class Node(T t)
{
// No capture in this form. Data has a backing field, and you assign t to it.
public T Data { get; set; } = t;
}

private class Node(T t)
{
// Capture in this form. Data has no backing field, and t is captured as class state
public T Data => t;
}
private class Node(T t)
{
// No capture in this form. Data has a backing field, and you assign t to it.
public T Data { get; set; } = t;
}

private class Node(T t)
{
// Capture in this form. Data has no backing field, and t is captured as class state
public T Data => t;
}
MODiX
MODiX4w ago
Angius
sharplab.io (click here)
class Foo(int x){}
class Bar(int x) {
private int _x = x;
}
class Quz(int x) {
public int Foo() => x * 2;
}
class Baz(int x) {
public int X { get; set; } = x;
}
class Foo(int x){}
class Bar(int x) {
private int _x = x;
}
class Quz(int x) {
public int Foo() => x * 2;
}
class Baz(int x) {
public int X { get; set; } = x;
}
Try the /sharplab command! | React with ❌ to remove this embed.
Angius
Angius4w ago
See what code gets generated for each of those
Faker
FakerOP4w ago
2sec everytime there is a backing field, we have the wordings __BackingField when viewed in sharpLab?
333fred
333fred4w ago
The compiler chooses a name
Angius
Angius4w ago
Just look at what the members are, not at their names
333fred
333fred4w ago
The compiler can use whatever name it wants
Angius
Angius4w ago
No { get; set; }? It's a field But, generally, yeah
Faker
FakerOP4w ago
2sec
333fred
333fred4w ago
(That being said, if we changed the pattern we use for generating backing fields, we'd probably break the world...)
Faker
FakerOP4w ago
Let's consider one by one, what I understand from the class Foo, is that it doesn't have the backing field
No description
Faker
FakerOP4w ago
it just have the primary constructor
333fred
333fred4w ago
Correct
Faker
FakerOP4w ago
now what I understand from the class Bar, it does have a field, the private int _x thing
333fred
333fred4w ago
Also correct, but this isn't a backing field. It's a manually-declared field
Faker
FakerOP4w ago
yep 2sec 2 more left
Angius
Angius4w ago
You just know someone does some weird shenanigans with Roslyn and regexing for _BackingField :KEKW:
Faker
FakerOP4w ago
This one now
No description
Faker
FakerOP4w ago
I noticed that private int <x>P is before the constructor of Quz this mean it is a field also ?
333fred
333fred4w ago
Order doesn't actually matter: it just is a field because it looks like a field
Faker
FakerOP4w ago
ahh I see but why did that get generated? because we don't have any field declared in the class, just a non-static method I think
333fred
333fred4w ago
That got generated because Quz.Foo captured the primary constructor parameter
Faker
FakerOP4w ago
ahh
333fred
333fred4w ago
Every time you invoke that method, it needs to run x * 2 So it needs to know what x is
Faker
FakerOP4w ago
yep last one
333fred
333fred4w ago
That's capturing
Faker
FakerOP4w ago
2sec ohh wait last one then we will reflect on what happen
Faker
FakerOP4w ago
No description
Faker
FakerOP4w ago
here it is, the magic word
333fred
333fred4w ago
The last one is most representative of what you originally wrote
Faker
FakerOP4w ago
"BackingField" !!!! so here, normally it has a backing field right? but their is no capture ? because here when we assign t to the property, their was no capture
333fred
333fred4w ago
Right. When you assign t to the property, that runs as part of the constructor So nothing needs to be captured
Faker
FakerOP4w ago
yep start to make sense, 2min I will just refresh and come back with some question/ a summary
Faker
FakerOP4w ago
small question
No description
Faker
FakerOP4w ago
in the last class Baz, notice that we have a backingField, but not for x right but for X ?
333fred
333fred4w ago
Precisely
Faker
FakerOP4w ago
alright, sooo from what I've understood (please correct me if I'm wrong): what we mean by "capture" is that, we want a particular variable to "persist". for example, in the example you gave, in the second class Node, we know that the method Data will always return t (when the primary constructor is called, t is passed as argument but then later on, if we need to modify data, how would we do it if t doesn't exist, so t is captured.) In the first class of node though, in the property Data, we set the value of Data to t; the value of Data is set only once, during instantiation; then we don't bother about what t is, so t is discarded (not captured) ?
333fred
333fred4w ago
Yup
Faker
FakerOP4w ago
Thanks !! I believe I now have a clearer understanding how all that works behind the scenes; I can close the post now 😂 thanks too @Angius 💯

Did you find this page helpful?