C
C#2y ago
230V

✅ a class implementing a generic interface with more than one interface as one generic type argument

I'm making a serializer (not for JSON), trying hard to use generics and trimmable methods wherever possible. I have:
interface IConverter {} //exists just for the attribute
interface IConverter<T>
{
//the methods could be static, but I'd need to use a bit more reflection than just Activator.CreateInstance<> to call them
void Serialize(TextWriter w, in T t, ...);
void Deserialize(TextReader r, ref T t, ...);
}

///usage: type T { [Converter<X>] V v; } class X : IConverter<V> {}
///built-in converters, such as ParseableFormattableConverter, will be chosen from a dictionary
class ConverterAttribute<T> : Attribute where T : IConverter //can't do T : IConverter<?>
//ex.:
class A : IConverter<Q> {}
class B : IConverter<W> {}
interface IConverter {} //exists just for the attribute
interface IConverter<T>
{
//the methods could be static, but I'd need to use a bit more reflection than just Activator.CreateInstance<> to call them
void Serialize(TextWriter w, in T t, ...);
void Deserialize(TextReader r, ref T t, ...);
}

///usage: type T { [Converter<X>] V v; } class X : IConverter<V> {}
///built-in converters, such as ParseableFormattableConverter, will be chosen from a dictionary
class ConverterAttribute<T> : Attribute where T : IConverter //can't do T : IConverter<?>
//ex.:
class A : IConverter<Q> {}
class B : IConverter<W> {}
Now I want to make a single converter that forwards to IParseable and IFormattable methods, but I can't see how this could be done.
///handles everything that's IParseable and IFormattable, such as DateTime, TimeSpan, long, int, short
///to avoid one converter per each built-in common type (would be an STJ moment)
class ParseableFormattableConverter : IConverter<TCombination/*???*/>
where TCombination : IFormattable, IParseable<?????>
{
void Serialize(TextWriter w, in TCombination t) => t.ToString(null, null);
void Deserialize(TextReader r, ref TCombination t) => t = ref TCombination.Parse(r.Read()); //more or less
}
///handles everything that's IParseable and IFormattable, such as DateTime, TimeSpan, long, int, short
///to avoid one converter per each built-in common type (would be an STJ moment)
class ParseableFormattableConverter : IConverter<TCombination/*???*/>
where TCombination : IFormattable, IParseable<?????>
{
void Serialize(TextWriter w, in TCombination t) => t.ToString(null, null);
void Deserialize(TextReader r, ref TCombination t) => t = ref TCombination.Parse(r.Read()); //more or less
}
16 Replies
333fred
333fred2y ago
So, not to be too mean about it, but have you seen https://github.com/serdedotnet/serde ?
GitHub
GitHub - serdedotnet/serde: Serde.NET is a C# port of the popular S...
Serde.NET is a C# port of the popular Serde serialization library for Rust - GitHub - serdedotnet/serde: Serde.NET is a C# port of the popular Serde serialization library for Rust
333fred
333fred2y ago
Andy's already done the hard work to make an aot and trimming-compatible (de)serializer
230V
230VOP2y ago
it seems to be a json serializer, I'm making mine for a different format example:
[Keyed, Separator(Field=":", List="|")]
class C
{
[Index(1)]
int I { get; }
[Index(2)]
string[] S { get; }
[Index(3), Separator(List="~")]
char[] C { get; }
}
Console.WriteLine(Serialize(new C { I = 3, S = { "as", "df" }, C = "zxc".ToArray() }));
[Keyed, Separator(Field=":", List="|")]
class C
{
[Index(1)]
int I { get; }
[Index(2)]
string[] S { get; }
[Index(3), Separator(List="~")]
char[] C { get; }
}
Console.WriteLine(Serialize(new C { I = 3, S = { "as", "df" }, C = "zxc".ToArray() }));
1:3:2:as|df:3:z~x~c
333fred
333fred2y ago
I believe you should be able to add new formats to it
Serde-dn is a multi-format serialization library, with built-in support for JSON.
@agocke can elaborate more
agocke
agocke2y ago
Yup, it should be extensible. Which format were you thinking of?
230V
230VOP2y ago
this
333fred
333fred2y ago
Looks CSV-like?
agocke
agocke2y ago
The problem will be changing the format in the source generator. The general idea is that you should split your format and your source generation Othewrise you can't really share a single source generator for every format The actual code that the source generation emits looks like
void Serialize(ISerializer serializer)
{
var typ = serializer.SerializeType("C", 3);
typ.SerializeField("I", this.I);
typ.SerializeField("S", this.S);
typ.SerializeField("C", this.C);
}
void Serialize(ISerializer serializer)
{
var typ = serializer.SerializeType("C", 3);
typ.SerializeField("I", this.I);
typ.SerializeField("S", this.S);
typ.SerializeField("C", this.C);
}
I do have prototype support for getting the attribute info, but it's opt-in per type and could be slower since it needs to be init'ed So my question here is: do you control this format, or are you forced to use it? If you are forced to use it, this might be hard, since this format has a strong connection between exactly what gets outputted and the source code. Serde is designed for a looser connection
230V
230VOP2y ago
forced to use it
agocke
agocke2y ago
Yeah, sorry. Maybe you can fork Serde and save yourself some effort? If you want a trim-compatible serializer, you will almost certainly need a source generator
230V
230VOP2y ago
yeah, I see that using Type.GenericTypeArguments produces IL2065 Value passed to implicit 'this' parameter of method 'System.Type.GetInterface(String)' can not be statically determined and may not meet 'DynamicallyAccessedMembersAttribute' requirements. no matter what I think I'll just not make it trim-compatible if there isn't a ConverterAttribute specified, I'll search in a list of types for a Converter<T> implementation that can be used - I need to get the T from it and check if T is compatible with the type that will be (de)serialized (this check differs slightly between reference types and value types) anyway, the interface problem is still present
agocke
agocke2y ago
Yeah there's no fundamental way to do transitive analysis in a trim-compatible way with reflection
sibber
sibber2y ago
isnt source genned STJ trim compatible?
230V
230VOP2y ago
I figured out a workaround, but for some reason I can't use IParsable
interface IConverter<T>{}
class Converter<T, I1, I2> : IConverter<T> where T : I1, I2 {}
class ParsableFormattableConverter : Converter<int, IParsable<int>, IFormattable> {}
interface IConverter<T>{}
class Converter<T, I1, I2> : IConverter<T> where T : I1, I2 {}
class ParsableFormattableConverter : Converter<int, IParsable<int>, IFormattable> {}
- The interface 'IParsable<int>' cannot be used as type argument. Static member 'IParsable<int>.Parse(string, IFormatProvider?)' does not have a most specific implementation in the interface.
- The interface 'IParsable<int>' cannot be used as type argument. Static member 'IParsable<int>.Parse(string, IFormatProvider?)' does not have a most specific implementation in the interface.
Wait, no. It uses int explicitly which isn't what I want painmaxima just no way without some feature like extensions or something that would let me say "whatever implements I1 and I2 is X, and I have class C : IConverter<X>" I'll have to have a bool CanConvert(Type) method somewhere for types that can't be generic Oh, this works ...but I really wish the <T> wouldn't need to be here, I'll need to validate that T == member return type (I won't, it will be a default one, but I will need to construct a generic type with e.g. int as a type arg 😔)
class ParsableFormattableConverter<T> : IConverter<T> where T : IParsable<T>, IFormattable {}
//[Converter<ParsableFormattableConverter<int>>] int i;
class ParsableFormattableConverter<T> : IConverter<T> where T : IParsable<T>, IFormattable {}
//[Converter<ParsableFormattableConverter<int>>] int i;
agocke
agocke2y ago
I'd still recommend ripping off as much of Serde as possible. It's a relatively clean design, so I think you would only have to alter a few places. In particular, there's functionality to pass down all the attributes on a member that I use for XML. That could get you 90% of the way to your implementation The only problem is that it's slower than not dealing with attributes, but you're going to do that anyway, so I'd bet it's likely as fast as anything else that uses attributes
230V
230VOP2y ago
I'll consider using serde in future, definitely serialization logic can be sg'd here For now I want to make this work and move on to next things
Want results from more Discord servers?
Add your server