|
the problem I'm running into right now regarding specialization i had to work around by using a common base class to "share functionality" and it doesn't work very well. I have a lot of duplicated code in the specialization, and i've had to use "new" on some methods. It's rather awful, actually.
Luckily most of the time, I can avoid the need altogether but in this case, specialization would make the most sense, so i did my best to "make it work"
it's baling wire and chewing gum compared to what i can do in C++
I guess for me, once I relearned C++ the way it's taught in Moo and Koenig's book, I used generic programming for most of my coding.
It doesn't make maintenance snowball. If it does, you're probably using it wrong and I highly recommend Accelerated C++ by the two authors I alluded to above. It's worth every penny of the $20-$30 asking price and you can usually get it used for a song.
It's also a mercifully short book. It's one of the best programming books I've ever encountered for that - it's concise, accessible, and teaches something that's not typically taught well (C++ programming)
But alas, using C++ the way they show you to use it makes me miss being able to do things that way now.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
As someone else mentioned, have you considered using interface?
Interface offer some of the benefit of multiple class inheritance without any drawback or ambiguity.
|
|
|
|
|
for this particular problem I don't really need multiple inheritance. I have a single inheritance chain.
Frankly, what I need is template specialization and if i had it there'd be no inheritance at all.
The use of FA as the base class is just a way to drag common code between the main FA and the CharFA specialization
the FA serves as the base class and the main class. The CharFA inherits from FA and specializes it.
It's ugly under the hood but it works.
The why of this would be easier to explain with a background in finite automata and regular expression engines
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
so you have (taking a guess in the dark)
public class SomeParsingData {}
public class FA<T>
{
public virtual void MyOperation(SomeParsingData data) {}
}
public class CharFA : FA<char>
{
public override void MyOperation(SomeParsingData data) {}
}
static class FAUtil
{
public static void MyOperation<T>(FA<T> target, SomeParsingData data) => target.MyOperation(data);
}
And you are lamenting that MyOperation() implementation is in FA instead of FAUtil class. Is it?
Particularly when FA and SomeParsingData are not really related but both needed in MyOperation, right?
Thinking about it... But not thinking too much until you confirm your issue...
|
|
|
|
|
Actually I have only two classes, with
class FA<TInput,TAccept> {
}
and
class CharFA<TAccept> : FA<char,TAccept> {
}
Most of the time, the second class delegates to the first.
Sometimes it has to overload what the base does. Sometimes it changes the function signature or "overloads" a static method so i have to use the "new" keyword.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
In answer to your question, I'm lamenting that
a) i can't share more code. i'm overloading way too much in CharFA
b) the two classes are distinct when they shouldn't be
I'd much rather have
var fa = new FA<char,string>();
var fa = new CharFA<string>();
both issues would be addressed by using partial template specialization in C++
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
It's funny... I remember once I had generic code that look kind of like that
void DoSomething<T>(T value) {
switch (typeof(T)) {
case typeof(double): DoDoubleThing((double)value); break;
case typeof(int): DoIntThing((int)value); break;
default: DoDefaultThing(value); break;
}
}
But after some refactoring this all went away...
I know, not helping, just sharing!
|
|
|
|
|
there's a kind of switch for types in newer C# but i've not used it yet. that might have been what it was.
unfortunately it doesn't solve my problem =/. I think i've worked around it well enough, i just wish i had something better. if i ever come up with a trick to solve it i may publish here about it.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
isn't that similar enough to template specialisation?
class A<T>
{
public virtual void Do(T value)
{
Console.WriteLine("Value: " + value);
}
}
class B : A<int>
{
public override void Do(int value)
{
Console.WriteLine("Int: " + value);
}
}
class Program
{
static void Main(string[] args)
{
A<int> a = new B();
a.Do(1);
}
}
|
|
|
|
|
that's exactly what I want. Does .NET support that now?
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I dunno if there was a problem before.. but it's copy paste from some code I was just running on a test project while thinking about your problem....
So, shortly, this is fine.
Assuming it was not always working (which I doubt) the test project use .NET Framework 4.7.2 and C# compiler latest version, i.e. 7.3
[EDIT & REMARK]this looks like perfectly valid C# since the beginning of generic to me. Odds are you got confused at some stage...
modified 31-Jul-19 0:36am.
|
|
|
|
|
i looked at your code wrong, didn't notice until i tried writing one myself. that's not template specialization, but simply method overloading - and i'm doing it already
class CharFA<taccept> : FA<char,taccept> {
..
}
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Yeah, I was wondering, isn't that good enough?!
But then I realised one could accidentally instantiate new FA<char, T>() instead of the desired new CharFA<T>()
But then what of this other syntax?
While it's slightly more wordy, I bet the end compiled result is just as you desired
class A<T>
{
public void Do(T value)
{
if (value is int intV) Do(intV);
else DoDefault(value);
}
void DoDefault(T value)
{
Console.WriteLine("Value: " + value);
}
void Do(int value)
{
Console.WriteLine("Int: " + value);
}
}
class Program
{
static void Main(string[] args)
{
A<int> a = new A<int>();
a.Do(1);
}
}
I mean personally I am happy to solve that using subclasses or interfaces, but since you really didn't want to....
|
|
|
|
|
Yeah I can't do that in this code because this code is inner loop critical and the "is" comparison is just a dog. I think "as" is faster, but still, it should be resolved at compile time.
I know it seems a minor quibble but this code may be used as part of a lexer. The lexing itself needs to be balls quick to be feasible.
also i'd be concerned about bugs this could introduce since i have to repeat a lot of code, but that's not a showstopper. it's just irritating
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I think you probably underestimate the compiler here
generic code are some sort of IL that is used to generate code on demand (when a concrete type is used)
when, say A<int> type is created the compiler will see that
void Do<int>(int value) {
if (value is double d) {
}
if (value is int ii) Do (ii);
} some path will never happen and it will removed them from the concrete implementation created when the concrete type is created.
and the compiled code will become
void Do<int>(int value) {
Do (value);
}
|
|
|
|
|
The reason I underestimate the compiler maybe is the last the time I really examined IL was in the .NET 2.0-3.0 days and the compiler didn't do hardly anything for program optimization.
Microsoft's rationale seemed to be that JIT would take care of it, but JIT doesn't do whole program optimization. It can only do peephole optimization, so I don't know what they were thinking. My guess is it was an excuse due to deadlines.
So I don't trust the compiler that much. Maybe my information is old. The compiler certainly has been revamped since then.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I mean it's such an obvious static type check optimisation.. I haven't explicitly checked for it, but I would wage $5 on it!
void Do<int>(int value) {
if (value is int ii) {
}
if (value is double dd) {
}
}
|
|
|
|
|
based on my experience, I'd take that bet.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
well.. my assembly is a bit rusty (or almost non existent) I will let you judge...
But here my test C# code
class A<T>
{
public void Do(T value)
{
if (value is int intV) Do(intV);
else DoDefault(value);
}
void DoDefault(T value)
{
Console.WriteLine("Value: " + value);
}
void Do(int value)
{
Console.WriteLine("Int: " + value);
}
}
class Program
{
static void Main(string[] args)
{
A<int> a = new A<int>();
a.Do(1);
A<double> b = new A<double>();
b.Do(1.0);
}
}
here is the code for a.Do(1) and b.Do(1.0) using go to disassembly in visual studio
a.Do(1);
00F70898 mov ecx,dword ptr [ebp-40h]<br />
00F7089B mov edx,1<br />
00F708A0 cmp dword ptr [ecx],ecx<br />
00F708A2 call 00F70478<br />
00F708A7 nop
b.Do(1.0);
00F708C3 fld qword ptr ds:[0F708E8h]<br />
00F708C9 sub esp,8<br />
00F708CC fstp qword ptr [esp]<br />
00F708CF mov ecx,dword ptr [ebp-44h]<br />
00F708D2 cmp dword ptr [ecx],ecx<br />
00F708D4 call 00F704A0<br />
00F708D9 nop
I think there is no (assembly) if statement and direct execution of the relevant if (type) branch....
|
|
|
|
|
there's a call in there that looks suspicious.
I'd need to see the IL, not the asm.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
release code atrocious too...
|
|
|
|
|
yep. The jitter can only do so much with peephole optimization. That's the ugly truth. Still, the IL will tell the tale. ILDASM or visual studio's decompile option should be able to grab it.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
well..
1. I was saying the compiler will remove obvious dead end code, make sense to check the assembly
2. what interesting is not the generic method's IL, but the concrete A<int> or A<double> code, which I have no clue where to see
at any rate, assembly code looks bad....
|
|
|
|
|
if i can see where that CALL in the asm leads i'd know, but viewing the IL is the only realistic way to tell where it leads
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Generic Do<T>() method's IL:
.method public hidebysig instance void Do(!T 'value') cil managed
{
// Code size 58 (0x3a)
.maxstack 2
.locals init ([0] int32 intV,
[1] bool V_1)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: box !T
IL_0007: isinst [mscorlib]System.Int32
IL_000c: brfalse.s IL_0022
IL_000e: ldarg.1
IL_000f: box !T
IL_0014: isinst [mscorlib]System.Int32
IL_0019: unbox.any [mscorlib]System.Int32
IL_001e: stloc.0
IL_001f: ldc.i4.1
IL_0020: br.s IL_0023
IL_0022: ldc.i4.0
IL_0023: stloc.1
IL_0024: ldloc.1
IL_0025: brfalse.s IL_0031
IL_0027: ldarg.0
IL_0028: ldloc.0
IL_0029: call instance void class ConsoleApp1.A`1<!T>::Do(int32)
IL_002e: nop
IL_002f: br.s IL_0039
IL_0031: ldarg.0
IL_0032: ldarg.1
IL_0033: call instance void class ConsoleApp1.A`1<!T>::DoDefault(!0)
IL_0038: nop
IL_0039: ret
} // end of method A`1::Do
|
|
|
|