Lecture thumbnail 0:00 / 0:00 In this lecture, we’re going to take a look at an adapter variation.

We’re going to take a look at yet another implementation of the adapter pattern.

But this one is completely different.

This one is unlike anything you’ve ever seen because it’s called a generic value adapter.

And it’s actually something very silly, as you’ll see in a moment.

It’s something that is completely unnecessary in languages like C plus plus, but in C sharp, unfortunately,

it is necessary.

And we’re going to take a look at how to construct it.

And we’re also going to kind of explore this approach a bit further and look at some of the other patterns

as well.

So this is going to be a rather feature packed demo, but essentially the idea is as follows Let’s suppose

that you want to implement vectors, you want to represent the idea of a vector.

Now a vector can be in two dimensional space or three dimensional or four dimensional space, whatever.

It can also be made up of different types.

So the components of the vector like X, Y and Z, for example, they can be integers, they can be

floating point values or double values or decimal or something else, and you want to have some sort

of generic approach to implementing all of this, using generics, using generic types.

At the end, what you want is you want to have types like vector two and vector three.

So you want to have a class for a two component, a vector of floating point values, a three component

vector of integer values and so on and so forth.

So you want all of this, and the question is how can you actually get it using generic programming?

Well, a very naive approach would be to just make a class called Vector, where you have two generic

parameters.

You specify the type of the elements that you want to store, and then you specify the dimensions.

How many dimensions does this vector actually have?

So you would kind of specify it like this, and then you would have some maybe array of data where those

components are actually stored and then you would have the constructor.

So in the constructor you have to initialize the data.

You would say data equals new array.

And here is the problem.

Here is where it’s entirely uncertain what you have to put inside the square brackets.

So you obviously want the array to match the number of dimensions like put it here, but that’s not

going to work because in C sharp, you cannot put literals inside generic types.

You can only have actual types in there.

So we cannot write something like Var.

So actually it’s not so much a var.

You cannot make a class called, let’s say vector to F, which inherits from vector float comma two

because you are not allowed to put the two in here.

And this leads to a design pattern called the generic value adapter.

Basically what you do is you adapt a literal, a literal value to a type, and it’s actually very simple

to do What you do is you make an abstract class or an interface which just yields that particular value.

So I can say, for example, public interface I integer, which just gives us a value.

So we only have a getter and then we can implement this interface in classes and have those classes

implement the interface and actually yield that particular value.

So instead of doing the whole thing at compile time, you’re doing that same thing at runtime.

So here I can build a class called two, which implements the I integer interface.

And here all I would have to do is implement a value and just return the value two and we can sort of

duplicate this, for example.

So if you want a three component something you would call the class three and here you would return

the number three.

We can actually make it a bit nicer by wrapping or sort of creating a synthetic class around these classes.

Or indeed you could use a namespace here if you wanted to, but I’ll just make a class just to because

basically these classes, they kind of exist at the top level of the namespace that we have, and that’s

not always a good thing.

So I might make something like the following public static class dimensions.

So the dimensions are two three, and you can add other things here.

So we’ve basically taken a value like 2 or 3 and we’ve adapted the value using a class.

So it’s a different kind of an adapter.

But now this whole thing becomes roughly legal because here we can say that D is an I integer, so we

can say that D is an I integer and furthermore it has a default constructor.

That’s another requirement in order to be able to actually initialize it.

So here, instead of using D, you would make a new instance of D and then you would get its value because

well, remember, it’s an I integer, so it has a value and this is now legal.

So this whole construct is now legal.

You can now start actually manufacturing types.

So for example, if you wanted, let’s say you wanted a two component integer, you would make a class

called Vector to I and you would inherit from a vector where the first argument is the type.

So in this case, int obviously.

And the second argument is the name.

Number of dimensions.

So you say dimensions dot two.

There we go.

So.

So this is something that’s completely valid and there is no problem in actually getting it to work.

We can actually start using it so we can say things like var V equals new vector to I and you know,

you can you can customize the data here.

But in order to customize the data, of course you have to give the vector additional features.

Like for example, you might want to give it an indexer so people can actually address the different

elements.

So coming back to the vector here, what you can do is you can say public to this with int index and

here you for the getter, you say you get the data at this particular index.

And for the setter, well, it’s obvious stuff.

You say data at index equals value.

So this is how you would set the whole thing up and this would allow you to actually manipulate the

different components.

So you can say that the first component of V is equal to zero.

Or if you want the whole X, Y, and Z business like like we have in geometry, you can have that as

well, except this is where you really lose flexibility because we’re not doing code generation, we’re

doing generic programming.

So here, for example, for the vector, what you can do is you can say, Well, let’s have a public

property of type T called X, and that’s going to be the first element.

So for the getter, we return data at position zero and for the setter we have data at position zero

equals value.

However, this quickly becomes kind of weird because if you add also the Y and the Z, those Y and Z’s

are going to be there even if you have a two dimensional vector.

So you have a Z, but with a two dimensional vector, it doesn’t really make any sense and it’s actually

dangerous to use it.

So as you can see, you’re kind of losing some of the functionality here already in terms of, you know,

losing some of the flexibility.

But on the other hand, you’re gaining quite a lot as well.

So what might you want to do with this kind of construct?

Well, you might want to, for example, add two vectors together.

That seems like a sensible thing.

You have one vector, you have, let’s say another vector.

Um, vector two I, which also has a bunch of values.

And by the way, let’s suppose you want to constructor once again if you want to constructor.

This is something that can be set up in the base class so you can have it in the vector, but then you’ll

have to propagate the constructors.

And let’s actually take a look at how this is done.

So suppose you want to take a bunch of values and you want to initialize the vector out of those values.

The question is how many arguments should you take?

Because you cannot write.

You cannot write some sort of vector where you take a t, x, t, y, t, z, and so on, because you

don’t know how many arguments there are.

So the only situation, the only construct that solves this problem is params.

Params is what solves this problem.

So here you specify the number of values that you want to assign to a particular vector when you initialize

it.

And then of course you have to do the actual work.

You have to figure out that what data you’ve got and how to assign it.

So it looks something like this.

You can see there’s plenty of code here.

So there is a required size, which is the size of the array you’re going to store, obviously relating

to the dimensions of the data structure, but also the provided size can be different.

So for example, you have a three dimensional vector, but you’ve only provided two elements because

you find it convenient to just initialize the X and the Y, leaving the Z alone.

So here we take the minimum.

We take the minimum of the required size and the provided size and basically assign only those values.

That makes it kind of the safest approach out there.

And what you can now do is you can you can sort of add the constructor arguments here, except it’s

not going to work just yet.

So in order to get this to work, you have to jump into vector 2D and you have to, um, you have to

basically create the appropriate constructors.

Now cogeneration is very easy nowadays using various developer tools, but this is one of the two approaches.

So there is an alternative approach which takes us into the realm of factory methods and recursive generics.

Now this is a more advanced approach, so it’s going to look particularly complicated, but I do want

to talk about it.

But before we talk about it, we need to talk about something else, which is also very important,

and that is how to perform actual operations on these vectors.

Because what I want to be able to do is I want to be able to say var result equals v plus v, v.

And unfortunately at the moment this is completely impossible because these things don’t have an operator.

Plus, furthermore, what you cannot do is you cannot go back into the vector class that we’ve made

here and you cannot give it an operator plus because remember, an operator plus cannot operate on generic

types.

This could be a string or well, strings also have operator plus.

So this could be something that doesn’t support operator plus like, I don’t know, a grid, for example.

So in this.

Particular case, What you have to do is you have to expand the inheritance hierarchy and kind of do

what you would call partial specialization in C plus plus you would specialize the vector class to integers

and then you would inherit from that.

So let’s actually do that.

Let me just show you how this is done, because it’s an interesting study.

So you’d make an in-between class in this inheritance hierarchy.

Let’s go with vector of int.

So you take the dimensions, but you don’t take the type argument because it’s an integer vector.

So this vector of end obviously inherits from vector where you specify int as well as the number of

dimensions.

You have to have the same generic constraints as well.

So you say D has to be an integer and has to have a default constructor and in here you would maybe

replicate all of those constructors that we have from the base class because you have to propagate them

throughout the entire hierarchy.

So I’ve generated both the empty constructor as well as a params constructor, but what we’re interested

in is getting that operator plus to work because we want to get this expression to be legal and we can.

We can have our cake and eat it too in this particular location.

So here we want to add the two vectors.

So public static vector of int with certain dimensions.

Operator Plus.

Okay, so we take the left hand side and the right hand side, left hand side, right hand side, and

then we basically perform the operation.

So we make the result basically a new vector of int d we get the dimensions.

So once again Vadim equals new D dot value.

That’s our generic value adapter work here.

And then we just make a for loop where we perform the copying.

So we basically say result at I equals left hand side at I plus right hand side at I like so and then

we can return the result.

And similarly we could cut and paste this entire method in order to support operator times.

For example, if you wanted to do element wise multiplication or division or subtraction or whatever.

But basically what this, this chunk of code, this operator plus implementation now would make this

thing legal.

But we have to modify the inheritance hierarchy because remember, vector to I at the moment is still

just an ordinary vector of int and dimensions to it now has to be a vector of int and specifying the

dimensions like so.

And now everything is legal.

All of a sudden the operator plus is a valid operator.

Everything, everything is working basically.

There’s absolutely no problem.

So as you may have noticed, we’ve had to perform lots of code generation for generating the constructors

because I really like the params constructor.

I really like being able to call the base params constructor, but you have to kind of jump from here

to here and then all the way up to here and it’s a lot of work just taking each of these derived classes

and just, just giving every single one of these two constructors.

Maybe, maybe we can avoid this because if we didn’t have to generate this constructor, then the default

constructor would be generated automatically even if you didn’t have it.

So the question is how can we actually avoid this entire approach?

Now I’ll leave the default constructors here, but let’s make another vector public class vector.

Let’s say we have a two dimensional one again.

Well, let’s have a three dimensional one vector three F, So vector three F is going to be a vector

of float.

So once again, as soon as you introduce a new type, you have to basically have a new class such as

like here we have vector of int, you would similarly have a vector of float.

So without the operator plus it’s actually very easy to construct.

You would just copy it like so you would change of int to of float here and then you would put float

here and obviously that’s well that’s pretty much it because you don’t have to do anything else.

So this vector of float just uses floating point numbers.

You specify the dimensions as three like so and that’s pretty much it.

Now let’s suppose that we want to avoid the generation of the constructors and constructor propagation.

Maybe we want our base class to just provide a factory method because methods can be inherited and Vector3

would inherit any factory methods of the base vector class.

So how can we do this?

Is it even possible to make a factory method up above here?

So let’s let’s try this.

Let’s go into Vector.

So here we are in vector and we’re going to try to we have a params constructor, but we want something

different.

We want a params factory method.

So we basically want some sort of public static vector of t comma d create, which takes params t values.

Now you’ll see it all go completely wrong in just about a second.

But the naive implementation would be something like the following.

You basically return a new vector of t d where you specify the value.

So you call the constructor, which is right here, and you return the result of that constructor.

Now, unfortunately, this approach isn’t going to work.

It simply isn’t going to work.

Let me show you why it’s not going to work.

Let’s suppose that we actually decide to use this approach and from the outset it would look like everything

is okay.

Var u equals vector three f dot create and provides some floating point values.

3.5 f 2.2 f one.

So this is legal now.

What is the problem if everything works?

Like what is the problem?

The problem is that as soon as you customize vector three F in any way, this entire thing just fails.

Because if you look at this var here and if you actually specify the type explicitly, this is not a

vector three F, this is a vector of float and dimensions dot three.

Now why is this important?

It’s important because let’s suppose we decide to go into vector three F and we decide to override.

Just make a custom two string for example.

So you make a custom two string where you do something fancy, like you try to you try to maybe, I

don’t know, string dot, join the components using a comma.

So take the data string, join.

So now you’ve customized this type.

Unfortunately, if you do u dot two string, you’re not going to be calling this method at all because

like I said, the result of the variable is a vector of float and dimensions three whereas this type

inherits from vector of float dimensions three so it’s not even a vector of float, it’s just an ordinary

vector.

Another thing, by the way, is that you cannot write something like u plus U.

It’s no longer valid because once again a vector three of dot create does not give you a vector of float,

and it’s precisely the vector of float that has the operator plus.

So it looks like a terrible approach.

It looks like the factory method isn’t going to work, but in actual fact we can make it work.

And the thing that makes it work is recursive generics.

Now, recursive generics are it’s a it’s a really kind of rare approach.

It’s an approach that is used in some of the other design patterns that we discuss here.

And basically what this means is that when you inherit from a vector of float, which in turn inherits

from vector, you have to propagate additional type information up to the base class about the return

type of your factory.

So here, instead of returning vector of TD, you have to return some special type, let’s call it t

self.

So this is the kind of typical approach of factory methods which try to return the type of the derived

object, the most derived object.

But of course this requires quite a bit of work.

So if we go into the vector class, we now need to add t self as one of the generic arguments here.

We also have to have additional constraints.

So t self here is going to have the recursive generic constraint.

So t self remember has to inherit from us.

It has to inherit from the whole vector TD thing.

So we just cut and paste it here.

It also has to have a default constructor as well.

So coming back here now, this entire approach basically changes this, this and we can no longer do

it like this because, well, it’s not a vector that we are constructing and it would be better to simply

replicate the kind of behavior that we have inside the params constructor, but in on a temporary variable

essentially.

So how do we do this?

Well, we need to return a t self, so we may as well start by creating it.

Var result equals new t self.

So far so good.

And then we can basically perform all of these operations.

So all of the operations on params.

But when you have data you just prefix it with result dot data and the same goes for result dot data

here and then you return the result.

So as you can see, it’s kind of difficult to actually reuse that constructor that we have.

So it’s maybe easier to just go with the factory method approach altogether and have everything kind

of self contained.

But the key, the key thing here is that we’re returning t self, we’re returning T self and because

of this we now have to change the entire hierarchy.

Like every single type that we have here, we have to change every single type to propagate this idea

of type information up above.

So when you.

How, for example, vector of float and D, what you have to do is you also have to modify basically

this type to also have additional type information.

Now, different ways of doing it, maybe you want to sort of specify it here or maybe you want to specify

it at this particular point.

So here you could say vector of float D being the underlying type.

So if somebody calls it on vector of float D, then you get a vector of float D, But you know, the

point is that you inherit further from this and the same would go for vector of int same recursive generic

approach.

So you would specify it here, but really you have to go down and you have to specify it in in these

types you have to have this vector to I and so on and all the rest of it.

You have to have basically all of this stuff working.

So remember, we wanted to avoid it in vector three F, So that’s perhaps where we would specify both

that and the propagated type as well.

So the modification will go something like this.

You would go back to a vector of float and you would modify this to also have a self.

So that’s particularly unpleasant because now you basically have to kind of propagate t self across

the entire thing, so to speak.

So so you take a vector of T self and then actually this has to be a, this entire thing passed in here.

So as you can see, it’s kind of getting really complicated.

It’s getting really convoluted.

I mean, I can sort of see what’s going on here, but it’s getting less and less readable.

But we are going to get some benefits from it.

Of course.

Now this part is invalid and now this part has to also be modified because you basically have to propagate

vector three F here as well before you propagate the dimensions.

But now what we have is we have this wonderful situation where if I take this Var and I specify the

type explicitly, you’ll see that we are still getting vector of float stuff.

And that’s, that’s not particularly good, is it?

Because we want the create invocation to actually give us a vector three F and at the moment we’re almost

there.

We’re almost there, but we’re not quite there.

So let’s try tracing through the type.

So we have a vector three, F a vector three F is it inherits from a vector of float and it specifies

vector three F in here.

So vector three F is the T self that is specified here.

And then vector here is a vector of float t stuff.

But in reality what happens is you have to specify I think to self here you have to specify t self here

and then you get additional constraints on top of that because we don’t know if that t self is, is

actually valid.

So we add an additional constraint that t self is new, but even that is not enough because t self must

also be convertible to a vector of t self float of d, which.

It as it stands.

It is in fact convertible.

Everything is valid, but you can sort of see the complexity rise as we do this.

So with all of these modifications, let’s go back into our declaration of Vector three F and take a

look at what the type is.

And finally, we have our final result.

So this is what we wanted in the first place.

We wanted the factory method to return the actual type.

So if I call it on vector three F, I get a vector three F if I call this on something else.

And we have added support for this yet, but if I call it on something else, then I get that something

else as a result.

So recursive generics are particularly helpful in here, but they do introduce quite a bit of complexity

because now every single declaration like a vector three declaration, just becomes this really messy

inheritance thing.

I mean, this is quite hard to understand for somebody who just wants to expand this hierarchy, like

add a new type, it now becomes fairly complicated.

But this is a legitimate approach.

This is something that does work and does allow you to have this this nice little setup.

So so the purpose of this entire lecture was to, first of all, talk about the generic value adapter,

which is a rather silly thing that we have to do in C sharp in order to propagate integer values or,

you know, any kind of literal values as arguments of a generic type.

So that is one thing.

But then we also discussed a couple of other things like how to expand the hierarchy so that you get

operations on numeric types, in which case you basically have partial specialization.

You have like if you want operator plus for example, you have to partially specialize.

The thing you have to say that here will specify explicitly that this is an INT thing and this loses

some of that generality because now if you want a vector of float, you have to have a vector of low

class, a vector of double class, blah, blah, blah.

So you’re not as generic as you want it to be.

And then of course, the icing on the cake here is to take a look at how to make a factory method,

which uses recursive generics to propagate type information across the inheritance hierarchy.

So that’s my example of the generic value adapter.

Play Play Play Stop Play Start Play information alert