How the .NET Runtime Loads a Type(mattwarren.org)
mattwarren.org
How the .NET Runtime Loads a Type
http://mattwarren.org/2017/06/15/How-the-.NET-Rutime-loads-a-Type/
53 コメント
This (somewhat) complicated process is also why generics can be fully reified in the CLR.
I love generics! Simplifies all my api creations deeply, huge re-use of code and very flexible implementations.
Eg. An API to a IoT cloud that lets user define their own type. The payload that i fetch is <T> so users can define their own type.
Another one is an api where i take a generic approach, so my only code that i write ( for every "class" / " model" ) is:
//every endpoint does a complete crud + jsonpatch + batch, also, all methods can be overridden to customize code. This is the code i need to write every time :p https://pastebin.com/fp8MtpBu
Note to myselve: extend the implementation to support expressions ( odata ) in the url ( similar to sql on top of an API through changing url parameters) and return the dto as response. :)
Eg. An API to a IoT cloud that lets user define their own type. The payload that i fetch is <T> so users can define their own type.
Another one is an api where i take a generic approach, so my only code that i write ( for every "class" / " model" ) is:
//every endpoint does a complete crud + jsonpatch + batch, also, all methods can be overridden to customize code. This is the code i need to write every time :p https://pastebin.com/fp8MtpBu
Note to myselve: extend the implementation to support expressions ( odata ) in the url ( similar to sql on top of an API through changing url parameters) and return the dto as response. :)
Very similar to something I'm working on. Except in my case I have an interface that the consumers implement to be able to talk to their service/database/whatever. It's designed for time series data (e.g., cpu usage, room temperature, that kind of thing). It's meant to only allow for numeric values and strings. But because there is no base type or interface for numeric types I unfortunately can't do something like:
:(
So instead the interface has an identical method for ints, doubles, floats, decimals, strings...
public Result WriteValue<T>(T value) where T : INumeric/Numeric { }
https://stackoverflow.com/questions/32664/is-there-a-constra...:(
So instead the interface has an identical method for ints, doubles, floats, decimals, strings...
My favorite taunt from the .NET team: according to the .NET Reference Source, built-in numeric types implement an IArithmetic<T> interface... And then they comment it out.
http://referencesource.microsoft.com/#mscorlib/system/double...
http://referencesource.microsoft.com/#mscorlib/system/double...
Here's an ad-hoc polymorphic way of dealing with numeric types in C# without the boxing that would be caused by using an IArithmetic interface:
public interface Num<A>
{
A Add(A x, A y);
A Subtract(A x, A y);
A Multiply(A x, A y);
A Divide(A x, A y);
A FromInteger(int value);
}
public struct NumInt : Num<int>
{
public int Add(int x, int y) => x + y;
public int Subtract(int x, int y) => x - y;
public int Multiply(int x, int y) => x * y;
public int Divide(int x, int y) => x / y;
public int FromInteger(int value) => value;
}
public struct NumDouble : Num<double>
{
public double Add(double x, double y) => x + y;
public double Subtract(double x, double y) => x - y;
public double Multiply(double x, double y) => x * y;
public double Divide(double x, double y) => x / y;
public double FromInteger(int value) => (double)value;
}
public struct NumBigInt : Num<BigInteger>
{
public BigInteger Add(BigInteger x, BigInteger y) => x + y;
public BigInteger Subtract(BigInteger x, BigInteger y) => x - y;
public BigInteger Multiply(BigInteger x, BigInteger y) => x * y;
public BigInteger Divide(BigInteger x, BigInteger y) => x / y;
public BigInteger FromInteger(int value) => (BigInteger)value;
}
public static class TestGenericNums
{
public static void Test()
{
var a = DoubleIt<NumInt, int>(10); // 20
var b = SquareIt<NumInt, int>(10); // 100
var c = NegateIt<NumInt, int>(10); // -10
var t = DoubleIt<NumDouble, double>(10); // 20
var u = SquareIt<NumDouble, double>(10); // 100
var v = NegateIt<NumDouble, double>(10); // -10
var x = DoubleIt<NumBigInt, BigInteger>(10); // 20
var y = SquareIt<NumBigInt, BigInteger>(10); // 100
var z = NegateIt<NumBigInt, BigInteger>(10); // -10
}
public static A DoubleIt<NumA, A>(A value) where NumA : struct, Num<A> =>
default(NumA).Add(value, value);
public static A SquareIt<NumA, A>(A value) where NumA : struct, Num<A> =>
default(NumA).Multiply(value, value);
public static A NegateIt<NumA, A>(A value) where NumA : struct, Num<A> =>
default(NumA).Subtract(default(NumA).FromInteger(0), value);
}That doesn't account for overflow, though...
Sorry, I don't follow?
The .NET implementation detects overflow and reports it back. This doesn't.
This:
var x = DoubleIt<NumInt, int>(Int32.MaxValue); // 20
Performs exactly the same as this: var a = Int32.MaxValue;
var b = Int32.MaxValue;
var c = a + b;
DoubleIt calls NumInt.Add which invokes the same code, why wouldn't it work in exactly the same way?I saw that too! WTF!
You would like Rails.
Beware though of improperly (or too eagerly) used generics. I've noticed they tend to spread like a cancer throughout a library and reduce the polymorphism of the code.
It's usually a good idea to be provide both an generic and a non-generic interface to an object.
A good technique I've found is to have the base object be non-generic and then have a generic version that overrides the properties (with new) that you want to use generic types on. (using as against an underlying field for conversion)
It's usually a good idea to be provide both an generic and a non-generic interface to an object.
A good technique I've found is to have the base object be non-generic and then have a generic version that overrides the properties (with new) that you want to use generic types on. (using as against an underlying field for conversion)
This is the most horrible advice I have ever seen on HN.
Well, that's flattering I guess :)
But it's not as bad as it sounds and the only way (AFAIK) to be able to work with both polymorphic objects and have the convenience (and type checking) of a generic counterpart. Limiting yourself to just a generic object/interface unfortunately breaks a lot of design patterns. It's also safe afaik since the only thing the generic version does is some casting and it's generic parameter enforces that casting.
but if you have a better way I'm genuinely interested to hear it
But it's not as bad as it sounds and the only way (AFAIK) to be able to work with both polymorphic objects and have the convenience (and type checking) of a generic counterpart. Limiting yourself to just a generic object/interface unfortunately breaks a lot of design patterns. It's also safe afaik since the only thing the generic version does is some casting and it's generic parameter enforces that casting.
but if you have a better way I'm genuinely interested to hear it
I agree with @ahoka that the advice is bad.
> Limiting yourself to just a generic object/interface unfortunately breaks a lot of design patterns.
Which ones and why?
> It's also safe afaik since the only thing the generic version does is some casting and it's generic parameter enforces that casting.
If you don't have a known constraint that is enforced by the type system then it can never be 'safe'.
> Limiting yourself to just a generic object/interface unfortunately breaks a lot of design patterns.
Which ones and why?
> It's also safe afaik since the only thing the generic version does is some casting and it's generic parameter enforces that casting.
If you don't have a known constraint that is enforced by the type system then it can never be 'safe'.
You can't pass generic interfaces to other classes that should handle non-generic interfaces (that aren't dependent on the generic constraint)
>If you don't have a known constraint that is enforced by the type system then it can never be 'safe'.
I don't quite know what you mean by this. If you cast to your "T" then casting is ensured. I guess there's some margin for error in that you must enter your casting code to be correct, but it's pretty hard to mess that part up, granted not as good as enforced genericy. Note that in many instances you only initialize generic classes, but you needed to be extract a non-generic interface from then to pass to other classes.
>If you don't have a known constraint that is enforced by the type system then it can never be 'safe'.
I don't quite know what you mean by this. If you cast to your "T" then casting is ensured. I guess there's some margin for error in that you must enter your casting code to be correct, but it's pretty hard to mess that part up, granted not as good as enforced genericy. Note that in many instances you only initialize generic classes, but you needed to be extract a non-generic interface from then to pass to other classes.
> You can't pass generic interfaces to other classes that should handle non-generic interfaces (that aren't dependent on the generic constraint)
I'd like to see an example of what you mean. You can absolutely pass generic values to other classes. If the type you're passing the generic value to is not generic then the method should be. If you are casting then 99 times out of 100 you have a problem with your code because the types aren't compatible. i.e.
If you 'get out' of the generic situation by casting to an object or dynamic, then at some point you're going to have to cast back to a concrete type. That is the point where your code will blow up. You're carrying ticking time bombs as values.
I'd like to see an example of what you mean. You can absolutely pass generic values to other classes. If the type you're passing the generic value to is not generic then the method should be. If you are casting then 99 times out of 100 you have a problem with your code because the types aren't compatible. i.e.
static void DoSomethingGeneric<A>(A value) => ...
static void DoSomethingSpecific(string value) => ...
IEnumerable<int> array = new [] { 1, 2, 3, 4, 5 };
DoSomethingGeneric(array.First()); // Works
DoSomethingSpecific(array.First()); // Can only ever work if array is a string[]
> If you cast to your "T" then casting is ensured.If you 'get out' of the generic situation by casting to an object or dynamic, then at some point you're going to have to cast back to a concrete type. That is the point where your code will blow up. You're carrying ticking time bombs as values.
Hi, sorry for the delay, had to pop into a meeting. I tried to make a very scaled down example of what I mean. It's hard to imagine a real-world scenario from this but assume that according to the architecture we need to separate concerns this way.
The point is that in some cases you want to be able to pass a more general interface while getting some Type-checking and convenience of generics.
In this case we want to hold on to PopProtocolSender and perhaps use it as a variable somewhere. When we use it we need to acccess it's specific IPopProtocol Protocol Property. But we also want to be able to treat it as a general IProtocolSender that has a IProtocol Property, so it can be sent to other classes that takes that interface.
These kinds of situations mainly arise when you have a more complicated architecture. If there's a better way to have your cake and eat it too I'd be glad to know it :)
The point is that in some cases you want to be able to pass a more general interface while getting some Type-checking and convenience of generics.
In this case we want to hold on to PopProtocolSender and perhaps use it as a variable somewhere. When we use it we need to acccess it's specific IPopProtocol Protocol Property. But we also want to be able to treat it as a general IProtocolSender that has a IProtocol Property, so it can be sent to other classes that takes that interface.
These kinds of situations mainly arise when you have a more complicated architecture. If there's a better way to have your cake and eat it too I'd be glad to know it :)
public interface IPopProtocol : IProtocol
{
void SomeUniqueMetod();
}
public interface IProtocol
{
}
public interface IProtocolManager
{
void SendMessage(IProtocol protocol, string message);
}
public interface IProtocolSender
{
IProtocol Protocol
{
get;
}
}
public class PopProtocol : IPopProtocol
{
public void SomeUniqueMetod()
{
}
}
public class Program
{
public void DoStuff()
{
var sender = new PopProtocolSender ();
var manager = new ProtocolManager();
manager.SendMessage(sender.Protocol, "Testing");
sender.Protocol.SomeUniqueMethod();
}
}
public class ProtocolManager : IProtocolManager
{
public void SendMessage(IProtocol protocol, string message)
{
}
}
public class ProtocolSender : IProtocolSender
{
protected readonly IProtocol _protocol;
public ProtocolSender(IProtocol protocol)
{
_protocol = protocol;
}
public IProtocol Protool => _protocol;
}
public class PopProtocolSender : ProtocolSender<IPopProtocol>
{
public PopProtocolSender() : base(new PopProtocol)
{
}
}
public class ProtocolSender<T> : ProtocolSender where T : IProtocol
{
public ProtocolSender(T protocol) : base(protocol)
{
}
public new T Protocol => (T)_protocol;
}I understand what you're getting at, but why not simply make both IProtocolSender and ProtocolSender generic. Here's a functioning example I based on yours that just compiled and ran in LinqPad without any issues:
EDIT:
Also please note that the signature for the generic IProtocolSender uses the out keyword, making it covariant. This will allow you to cast IProtocolSender<IPopProtocol> to IProtocolSender<IProtocol> if you need to, or to pass an IProtocolSender<IPopProtocol> object to a method that expects an IProtocolSender<IProtocol>.
public interface IPopProtocol : IProtocol
{
void SomeUniqueMethod();
}
public interface IProtocol
{
}
public interface IProtocolManager
{
void SendMessage(IProtocol protocol, string message);
}
public interface IProtocolSender<out T> where T : IProtocol
{
T Protocol
{
get;
}
}
public class PopProtocol : IPopProtocol
{
public void SomeUniqueMethod()
{
Console.WriteLine($"Hi, I'm {nameof(PopProtocol)}");
}
}
void Main()
{
var sender = new ProtocolSender<IPopProtocol>(new PopProtocol());
var manager = new ProtocolManager();
manager.SendMessage(sender.Protocol, "Testing");
sender.Protocol.SomeUniqueMethod();
}
public class ProtocolManager : IProtocolManager
{
public void SendMessage(IProtocol protocol, string message)
{
}
}
public class ProtocolSender<T> : IProtocolSender<T> where T : IProtocol
{
private T _protocol;
public T Protocol => _protocol;
public ProtocolSender(T protocol)
{
_protocol = protocol;
}
}
Not sure what the fuss is: you can use SendMessage as usual, and also have access to SomeUniqueMethod.EDIT:
Also please note that the signature for the generic IProtocolSender uses the out keyword, making it covariant. This will allow you to cast IProtocolSender<IPopProtocol> to IProtocolSender<IProtocol> if you need to, or to pass an IProtocolSender<IPopProtocol> object to a method that expects an IProtocolSender<IProtocol>.
because then a generic requirement will propagate through the whole framework. And some polymorphism/patterns simply won't be possible. Trust me, I've encountered this problem several times
I've been writing c# for around 12 years (on multi million line code bases) and have never had the problems you state, and the example you gave has been shown to not be an issue.
The idea that the propagation of type safety is an issue is something I find bewildering, sorry.
The idea that the propagation of type safety is an issue is something I find bewildering, sorry.
> because then a generic requirement will propagate through the whole framework.
And? Why is this a bad thing? Are you trying to maintain compatibility with .NET 1.0 for some reason?
> And some polymorphism/patterns simply won't be possible. Trust me, I've encountered this problem several times
Which patterns? Please demonstrate the problem.
And? Why is this a bad thing? Are you trying to maintain compatibility with .NET 1.0 for some reason?
> And some polymorphism/patterns simply won't be possible. Trust me, I've encountered this problem several times
Which patterns? Please demonstrate the problem.
> have a generic version that overrides the properties (with new)
is the horrible bit. You should almost NEVER override with "new".
is the horrible bit. You should almost NEVER override with "new".
I agree, this is one of the few exceptions, in fact the only, exception I encountered. But this is a solution for a very specific problem in certain solutions, not a something you tend to do all the time
It is not only good advice, it is required in fair amount of situations because of limitations in the C# language.
It's terrible advice. What limitations are you thinking of?
interface IValidator<T> {
bool Validate(T thingToValidate);
}
class ThingValidator : IValidator<Thing> {}
class ThingValidator : IValidator<OtherThing> {}
var validators = GetAllValidators();
What type is `validators`?What sensible thing could you do with a list containing a ThingValidator and OtherThingValidator? Assuming there's no relationship between Thing and OtherThing you're forced to use runtime checking to find the validator you need for something typed as object, so at that point you have to use casting or reflection, which you'd also need to do when implementing a non-generic interface.
You are thinking that using reflection and casting at runtime is not a sensible thing because it is not optimal, but you must consider that constraints in existing code bases will often make writing optimal solutions too costly.
An example is:
For some reason or another you've written a thread dispatcher that will dynamically spin up threads and route the outputs of these threads into new threads. (e.g. https://github.com/bilus/pipes )
Obviously this library will need to do run-time casting of types already as C# makes it too difficult to orchestrate these threads without some shared type. (Here is an example of Microsoft following this pattern. https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).... )
Now you need to hook validation into this existing dispatching library by assosciating validators with the outputs of particular threads. Without a shared type you cannot invoke the validator, but it sure is convenient to write the validator using generics.
Another example is:
You have two interfaces, "ISerializable" and "IEquatable", and two validators, "Validator<ISerizable>" and Validator<IEquatable>", and these validators apply to multiple classes. To run all of these validators against a given class you have three (un)reasonable choices:
* Create a boilerplate wrapper class for each of these validators for each class that they apply to, and store them against that class somehow. The disadvantage of this is that you lose track of which validators are shared between classes which may be important for some optimisation.
* Create a non-genic type "IValidator" or "Validator" that all validators share and put these in a collection. (i.e. as mentioned by the grandparent of this post).
* Bite the bullet: The code that calls these validators has to be duplicated. (This is the incorrect choice though because some day you may need to add a spin-up/teardown before running the validators and now you don't have a centralized location to do this)
Another example is:
You are writing validators for types from a 3rd party library. You may not modify the source code of this library, because it's WinForms. You've been tasked with a set of arbitrary validation tasks for your multi-million line codebase. Maybe you need to add a spell checker for all user-editable text? Maybe any control that is bound to a particular property needs an orange background.
Suddenly you have a case where you really do need to do casting and reflection to figure out which validators to run, as you perform the unfortunate evil of a recursive search through the entire control tree to hook up your validation, rather than re-writing a few thousand files.
An example is:
For some reason or another you've written a thread dispatcher that will dynamically spin up threads and route the outputs of these threads into new threads. (e.g. https://github.com/bilus/pipes )
Obviously this library will need to do run-time casting of types already as C# makes it too difficult to orchestrate these threads without some shared type. (Here is an example of Microsoft following this pattern. https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).... )
Now you need to hook validation into this existing dispatching library by assosciating validators with the outputs of particular threads. Without a shared type you cannot invoke the validator, but it sure is convenient to write the validator using generics.
Another example is:
You have two interfaces, "ISerializable" and "IEquatable", and two validators, "Validator<ISerizable>" and Validator<IEquatable>", and these validators apply to multiple classes. To run all of these validators against a given class you have three (un)reasonable choices:
* Create a boilerplate wrapper class for each of these validators for each class that they apply to, and store them against that class somehow. The disadvantage of this is that you lose track of which validators are shared between classes which may be important for some optimisation.
* Create a non-genic type "IValidator" or "Validator" that all validators share and put these in a collection. (i.e. as mentioned by the grandparent of this post).
* Bite the bullet: The code that calls these validators has to be duplicated. (This is the incorrect choice though because some day you may need to add a spin-up/teardown before running the validators and now you don't have a centralized location to do this)
Another example is:
You are writing validators for types from a 3rd party library. You may not modify the source code of this library, because it's WinForms. You've been tasked with a set of arbitrary validation tasks for your multi-million line codebase. Maybe you need to add a spell checker for all user-editable text? Maybe any control that is bound to a particular property needs an orange background.
Suddenly you have a case where you really do need to do casting and reflection to figure out which validators to run, as you perform the unfortunate evil of a recursive search through the entire control tree to hook up your validation, rather than re-writing a few thousand files.
> You are thinking that using reflection and casting at runtime is not a sensible thing because it is not optimal
No I'm not thinking that, I'm saying that implementing a non-generic base interface like
> Obviously this library will need to do run-time casting of types
You've linked to a clojure library which is dynamically typed so I don't see how this is relevant. It's not obvious to me why a C# version would require casting.
Task/Task<T> does follow the pattern but they could have simply introduced a Unit type in the BCL and replaced the non-generic Task type with Task<Unit> instead.
> To run all of these validators against a given class you have three (un)reasonable choices:
You only need to write one wrapper class which encapsulates the cast you'd have to do anyway in a non-generic IValidator interface:
No I'm not thinking that, I'm saying that implementing a non-generic base interface like
public interface IValidator {
bool Validate(object obj);
}
is pointless because you'll have to do the same type checking/casting in Validate you would have to do in a wrapper which implemented IValidator<object>. Requiring a base interface is worse because you'll have to implement the same boilerplate implementation of Validate(object) in every implementing class - see IEnumerable/IEnumerable<T> as an example.> Obviously this library will need to do run-time casting of types
You've linked to a clojure library which is dynamically typed so I don't see how this is relevant. It's not obvious to me why a C# version would require casting.
Task/Task<T> does follow the pattern but they could have simply introduced a Unit type in the BCL and replaced the non-generic Task type with Task<Unit> instead.
> To run all of these validators against a given class you have three (un)reasonable choices:
You only need to write one wrapper class which encapsulates the cast you'd have to do anyway in a non-generic IValidator interface:
public class Wrapper<T, TBase> : IValidator<TBase>
{
private readonly IValidator<T> inner;
public Wrapper(IValidator<T> v)
{
this.inner = v;
}
public bool Validate(TBase b)
{
if(b is T)
{
return this.inner.Validate((T)(object)b);
}
else return false;
}
}> Task/Task<T> does follow the pattern but they could have simply introduced a Unit type in the BCL and replaced the non-generic Task type with Task<Unit> instead.
Microsoft could have added some unified Unit type but they did not, which makes this irrelevant.
Just because there is an obvious negative consequence to choosing a particular type schema (e.g. IEnumerable<t> extending IEnumerable) does not mean that the pattern is pointless. Sometimes biting the bullet is necessary since C# has a bullshit type system that makes dynamic orchestration a pain in the arse.
Microsoft could have added some unified Unit type but they did not, which makes this irrelevant.
Just because there is an obvious negative consequence to choosing a particular type schema (e.g. IEnumerable<t> extending IEnumerable) does not mean that the pattern is pointless. Sometimes biting the bullet is necessary since C# has a bullshit type system that makes dynamic orchestration a pain in the arse.
> Microsoft could have added some unified Unit type but they did not, which makes this irrelevant.
It's not irrelevant - you're using Task/Task<T> as an example of the non-generic base type/generic subtype pattern being necessary, but it isn't.
IEnumerable<T> extending IEnumerable is a consequence of C#1 not supporting generics at all, and the non-generic version would not be necessary if it had. I was just using it as a common example of the boilerplate the pattern incurs.
It's not irrelevant - you're using Task/Task<T> as an example of the non-generic base type/generic subtype pattern being necessary, but it isn't.
IEnumerable<T> extending IEnumerable is a consequence of C#1 not supporting generics at all, and the non-generic version would not be necessary if it had. I was just using it as a common example of the boilerplate the pattern incurs.
[deleted]
The signature of GetAllValidators must be: GetAllValidators<T>();
So it's whatever type of T you pass to GetAllValidators.
So it's whatever type of T you pass to GetAllValidators.
Not to mention that in the original example IValidator is invariant...it can be far more flexible by making it covariant (IValidator<out T>) so that GetAllValidators can also be used on more derived types.
And then you can do for example:
:)
And then you can do for example:
var validators = GetAllValidators<EntityBase>();
Where validators might be IEnumerable<IValidator<EntityBase>>. Compile-time type safety is preserved and each individual validator's generic type can be any type that is derived from EntityBase.:)
[deleted]
The question here is, what good is a collection of all validators? If you want to perform validation, you're starting with an item (to validate) and you know its type, so it shouldn't be a problem to get the correct IValidator<T> for it. If you really need to have a common base for all validators, explicit interface implementation is a better choice than hiding methods with new:
interface IValidator
{
bool Validate(object item);
}
interface IValidator<T> : IValidator
{
bool Validate(T item);
}
abstract class Validator<T>
: IValidator<T> where T : class
{
public abstract bool Validate(T item);
bool IValidator.Validate(object item)
{
var typedItem = item as T;
if (typedItem == null)
throw new InvalidOperationException();
return Validate(typedItem);
}
}This code wouldn't compile, for starters.
But it could be `IValidator<object>`, in the worst case. That being said, why not make a `class ThingValidator<T> : IValidator<T> {}`, for all the lack of detail we have?
There aren't enough details here to demonstrate the weaknesses of generics.
But it could be `IValidator<object>`, in the worst case. That being said, why not make a `class ThingValidator<T> : IValidator<T> {}`, for all the lack of detail we have?
There aren't enough details here to demonstrate the weaknesses of generics.
>This code wouldn't compile, for starters.
Thats the point. You need an `IValidator` interface.
Thats the point. You need an `IValidator` interface.
The reason it won't compile is that the code, as given, has two classes with the same name.
> I also use dto's in some endpoints. So i should extend the implementation to support linq on the domain model ( on odata) and return the dto as response. But this is for later i suppose
Please don't. I've ended up maintaining far too many abominations where developers got excited about generics and linq rather than solving real problems. You just end up with apps that should be simple and straightforward being a nightmare to maintain.
Generics, linq and inheritance all have their uses but if they aren't used sparingly they create more spaghetti.
Edit - at the very least, please be careful and use generics as sparingly as possible.
Please don't. I've ended up maintaining far too many abominations where developers got excited about generics and linq rather than solving real problems. You just end up with apps that should be simple and straightforward being a nightmare to maintain.
Generics, linq and inheritance all have their uses but if they aren't used sparingly they create more spaghetti.
Edit - at the very least, please be careful and use generics as sparingly as possible.
Do you have any examples of how generics could create spaghetti code? Given that they aren't control flow constructs, I'm having a hard time imagining such a situation.
If you try to use generics too hard you make crazy shit. I'm working on a system that has a Something<T, Y> but the Y inherits from the T and its all just a mad mess that makes no sense at face value. I guess someone thought it was a good idea at some point but the end result is just confusing as hell.
Then you realise its just sitting upon 5 really simple Sql tables and you realise its really convoluted.
Its all about stepping back and going: "am I trying too hard?". Like that last 10% of making the code base 100% elegant is what ruins it.
Its all about stepping back and going: "am I trying too hard?". Like that last 10% of making the code base 100% elegant is what ruins it.
everything is great... in moderation. That last bit is what everyone fucks up (including myself in the past).
E.G. Composition + OOP is way better than "just" OOP.
Are there any benchmark of the cost in terms of performance paid by this process compared to other methods in other languages ? I suppose this happens only at load time, but i wonder if that doesn't have other impact elsewhere during the execution.
I'm pretty sure this process only has an impact during startup and even then I don't know if it's that expensive to load Types like this, but I agree, it would be interesting to measure it compared to a similar run-time (say Java)
If reflection is being monitored closely etc then it must have some impact. Probably tiny
Reflection has a whole load of other overheads (see http://mattwarren.org/2016/12/14/Why-is-Reflection-slow/ for some info) so I doubt that actually loading a Type is high up the list of time-spent, but I've never actually measured it.
That's a good point, I'd not thought about it like that.
So you're saying that unless you load types step-by-step, you can't have fully reified generics types, because of circular references/dependencies, is that the reason?
So you're saying that unless you load types step-by-step, you can't have fully reified generics types, because of circular references/dependencies, is that the reason?
Having implemented a large subset of this stuff for my own .NET runtime, you absolutely cannot implement the fully reified generics without loading in stages like this. The circular dependencies can get extremely tangled.
Thanks for the info
> Having implemented a large subset of this stuff for my own .NET runtime
Interesting, which runtime was this? Is it the source available anywhere, I'd love to have a look if it was?
> Having implemented a large subset of this stuff for my own .NET runtime
Interesting, which runtime was this? Is it the source available anywhere, I'd love to have a look if it was?