Replies: 22 comments 5 replies
-
I have just edited in a few clearer and more broken up parts to the detailed design section. I have also edited the summary to indicate a potential revision. I am no longer convinced that indirectly derived types should be passable as TSelf when declaring variables/fields/members as this represents a departure from existing generic behaviour and provides very limited value. I have however only made this edit to the summary and left the feature in other places in the document for the time being as I'm not completely sure. If anyone has any thoughts on this matter I'd be interested. |
Beta Was this translation helpful? Give feedback.
-
I'd love to see this feature. I've been using CRTP more and more frequently as I got more familiar and comfortable with it, but only internally so far and not for any externally consumable APIs. My favorite example use case for this is base Enumeration class, which allows consumers to derive it and inherit functionality similar to Java enumerations - each instance of a derived class gets an ordinal number and a string name properties, as well as overriden toString method and implicit casts to int and string, while each derived type itself gets a static property with a collection of all enumerations as well as static methods/index operators to access an enumeration by int or string value. This allows creating OO style enumerations with minimal boilerplate code. Some examples:
second example:
|
Beta Was this translation helpful? Give feedback.
-
@TomasJuocepis |
Beta Was this translation helpful? Give feedback.
-
I too use this pattern very frequently, it's very useful. In my extension method library the most prominent example is my class PropertyHistoryBase which manages all INotifyPropertyChanged handling and tracks history for each property. I then query the history using an expression.
Example:
|
Beta Was this translation helpful? Give feedback.
-
Can this proposal be expanded into simply being able to use
Fluent method chaining:
in addition to the aforementioned ability to use it as a type constraint:
|
Beta Was this translation helpful? Give feedback.
-
What you're describing is approximately #252. If you look there you'll see some discussion between myself and @phi1010 regarding the relative merits of each approach. In short, the answer to your question is "maybe". The functionality you're talking about would be nice, but it's a significantly larger feature to implement. This proposal effectively (this is a little simplified) just adds a compiler check to enforce the constraint, and otherwise relies on existing language features. The more comprehensive version (what you're advocating) would need many more changes, including significant syntax/parsing changes and other internal stuff that's a bit out of my comfort zone to talk about. So in conclusion, what you're advocating would be great, this would be easier. Perhaps this could be implemented first, and the other features could be added at a later date. Do go and vote on that one (and this one if you don't hate it) and let's hope we can get at least some version of this sometime soon. |
Beta Was this translation helpful? Give feedback.
-
Thank you for opening this subject, for me it is one of the most important features that I would like to see it available into C#. |
Beta Was this translation helpful? Give feedback.
-
If I understand this proposal correctly, the whole point of this feature would be to make CRTP safer. But CRTP is just a (rather awkward) crutch for a common problem, so it feels wrong to add a feature that would make it safer, but no less awkward. I'd rather see something that would make CRTP unnecessary. |
Beta Was this translation helpful? Give feedback.
-
1
Why only abstract classes? It seems to be applied to any kind of classes (except sealed). 2
I think that to write
3
A CatWithBell can make friends with a Cat. And if I want that a CatWithBell can make friends only with other CatWithBell then I should change code above to that?
But what if I write that? 4
I think it obviously shouldn't work because this class |
Beta Was this translation helpful? Give feedback.
-
While this proposal fiits in the current language, I believe it's wrong that After working some time with language like Rust I see that we just need a keyword Thus I'd like to just type: abstract class BaseType
{
public static this Default { get; } = new this();
protected BaseType() {} // make sure we have a default constructor
} If you place it in generics you have them spreaded up in all your system, you start writing generic methods just to access these animals, however it's clear that for example Alternatively, look at Rust depended types: trait Clone {
fn clone(&self) -> Self;
} Clean, honest declaration. We don't need generics here because we have no generic parameter. We actually need nothing else because it's ok as it is. I don't mind if it would be implemented via generics with some attribute to prevent user from diving into them, but writing it manually is a nightmare. |
Beta Was this translation helpful? Give feedback.
-
@Pzixel This doesn't make sense to me, since the same static field in the base class ( // ReSharper suggests that you correct this to BaseType.Default
// since they are two names for the same single field.
var x = DerivedType.Default;
// Outputs "true"; same field, holding the same instance, no matter how the name was qualified.
Console.WriteLine(ReferenceEquals(x, BaseType.Default));
abstract class BaseType
{
public static this Default { get; } = new this();
protected BaseType() {} // make sure we have a default constructor
}
class DerivedType : BaseType
{
} |
Beta Was this translation helpful? Give feedback.
-
@jnm2 You're right; it's not going to work with static members (at least, not inherited ones). This feature is applicable more to Shapes, I feel. |
Beta Was this translation helpful? Give feedback.
-
@jnm2 the problem is that we have no way to constraint against static methods. We cannot write interface public interface IParseable
{
public static this Parse(string input);
} This is why it looks ugly. I can propose to just shadow base declarations. E.g. it this code abstract class BaseType
{
public static this Default { get; } = new this();
protected BaseType() {} // make sure we have a default constructor
}
class DerivedType : BaseType
{
}
class DerivedDerivedType : DerivedType
{
} Compile into this: abstract class BaseType
{
public static BaseType Default { get { throw new InvalidOperationException(); } }
protected BaseType() { } // make sure we have a default constructor
}
class DerivedType : BaseType
{
public static DerivedType Default { get; } = new DerivedType();
}
class DerivedDerivedType : DerivedType
{
public static DerivedType Default { get; } = new DerivedDerivedType();
} Again, this doesn't look good, but again we have no abstract for static members. It's just a direction. Another thought: it's just how it could be implemented, on a source code level it's fine, except that it may be a bit confusing when you're trying to get this property via reflection, but again, it's learnable. |
Beta Was this translation helpful? Give feedback.
-
It's counter-intuitive for an arbitrary number of static fields to be generated when you replace the field type with a keyword. How common is this scenario? |
Beta Was this translation helpful? Give feedback.
-
@jnm2 happens quite often to me. For example I have following class: public abstract class ContractBase
{
protected readonly Web3 Web3;
public string Address { get; }
protected ContractBase(Web3 web3, string address)
{
Web3 = web3;
Address = address;
}
protected Nethereum.Contracts.Contract Contract => Web3.Eth.GetContract(Abi, Address);
protected abstract string Abi { get; }
} And I have to hold Abi in separated class public static class Abi
{
public static string SeasonFactory { get; } = XResource.ReadResource(nameof(SeasonFactory), "abi");
} And then use it in derived class public class SeasonFactory : ContractBase
{
public SeasonFactory(Web3 web3, string address) : base(web3, address)
{
}
public static async Task<SeasonFactory> DeployAsync(Web3 web3, params object[] values)
{
var receipt = await web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(Core.Abi.SeasonFactory, Core.Bin.SeasonFactory, web3.TransactionManager.Account.Address,
new HexBigInteger(4600000), values: values);
return new SeasonFactory(web3, receipt.ContractAddress);
}
protected override string Abi => Core.Abi.SeasonFactory;
} Note that So I'd like to write following: public abstract class ContractBase
{
protected readonly Web3 Web3;
public string Address { get; }
protected ContractBase(Web3 web3, string address)
{
Web3 = web3;
Address = address;
}
protected Nethereum.Contracts.Contract Contract => Web3.Eth.GetContract(Abi, Address);
public static string Abi { get; } = XResource.ReadResource(nameof(this), "abi");
public static string Bin { get; } = XResource.ReadResource(nameof(this), "bin");
} And then have: public class SeasonFactory : ContractBase
{
public SeasonFactory(Web3 web3, string address) : base(web3, address)
{
}
public static async Task<SeasonFactory> DeployAsync(Web3 web3, params object[] values)
{
var receipt = await web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(this.Abi, this.Bin, web3.TransactionManager.Account.Address,
new HexBigInteger(4600000), values: values);
return new SeasonFactory(web3, receipt.ContractAddress);
}
} And completely remove these separated |
Beta Was this translation helpful? Give feedback.
-
Did you consider to expand this into full associated types? LIke: interface IComparableRight {
type T = this;
public bool Compare(T other)
} |
Beta Was this translation helpful? Give feedback.
-
For this particular proposal? I haven't seen that. There is a proposal for associated types though: #1328. |
Beta Was this translation helpful? Give feedback.
-
Yep, didn't see this feature, probably because of strange syntax there. Thanks for linking. Hope it will be championed. |
Beta Was this translation helpful? Give feedback.
-
Associated/existential types as in #1328 look like a very nice feature that may (I would need to think about it more) be able to achieve the goals of this proposal, but I wouldn't consider merging them into this. This proposal was designed to be the smallest and least disruptive change that could achieve safe CRTP type behaviour. There are more advanced proposals that could also achieve that goal along with others, but that have a higher barrier to implementation. I think it's good to have a range of possible routes to something like this. |
Beta Was this translation helpful? Give feedback.
-
How did this subject evolved? |
Beta Was this translation helpful? Give feedback.
-
The new Gerneric Maths Interface use this kind of practice. I don't know where the code analyzer for this is located. But is it possible to generalize the to an attribue ? The CLR already supports this, however, exepete it also accepte things that we wouldn't want. A Warning or Error would be a real plus if the implementation is not right, like: [System.AttributeUsage(AttributeTargets.GenericParameter | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class SelfAttribute : Attribute;
public abstract class Base<[Self] TSelf> where TSelf : Base<TSelf>;
public class Foo : Base<Foo>;
// Error here
public class Bar : Base<Foo>; I also try: public class Base<[Self] TSelf> where TSelf : Base<TSelf>, IDisposable;
// CS0311
public class Foo : Base<Foo>; There is already the fact of forcing the derived member to implement interfaces. However, Intellisense does not offer a clear solution. Making Intelisence propose as a solution to implement the interfaces would make it more compressible and a time saving. Personally, I prefer a valid keyword for abstract classes and interfaces rather than having to make a Generic class. |
Beta Was this translation helpful? Give feedback.
-
still no |
Beta Was this translation helpful? Give feedback.
-
where T: this
generic type constraintPorting from dotnet/roslyn#11773 as while the proposal received minimal attention initially it's been a little more active recently. The text here is partially copied over and partially rewritten.
Summary
Note: TSelf is used as the type parameter name by convention here, but any name may be used.
The
where TSelf: this
generic constraint would be applicable to abstract classes and interfaces only. Type parameters with thethis
constraint would be constrained to being either the deriving type itself, or a furtherthis
constrained type parameter on the deriving type.When used outside of derivation (e.g. to type a variable) further derived children would also be allowed.I am no longer particularly convinced of the value of the struck-through feature! It represents a departure from existing generics behaviours (hopefully the only one) with little obvious return. It remains in other places in this document for now, but may be removed.Motivation
TL;DR: This is an attempt to vanquish Eric Lippert's evil dog, and to annoy physicists by violating thermodynamics to unbake a noodle.
Safe use of the (sort of) Curiously Recurring Template Pattern. Eric Lippert gives a pretty good overview and critique of this pattern on his blog. I believe this proposal addresses and solves every problem Lippert raises.
One of the main problems with the current use of the CRTP without the
this
constraint is that it's not safe, a consumer can fail to supply itself as the generic parameter. As such, this pattern while sometimes okay to use internally, is rarely reasonable to expose across an API interface. This constraint would fix that.Another common issue is that the code is confusing. With this constraint in place the compiler and IntelliSense would enforce correct behaviour and remove ambiguity, mostly fixing this.
I've found myself wishing for this a number of times over the last few years. I suspect the primary use cases would be in more framework like libraries than business logic.
A side-effect of this would be that an interface could restrict the types to which it can be applied.
where TSelf: this, Foo
would mean an interface can only be applied to class Foo and its children, andwhere TSelf: this, IBar
would mean the interface could only be applied to types that also implement IBar. While not exactly the primary intention behind this feature, I actually think this could be a nice thing to have when building strong APIs with specific consumption of interfaces in mind.A few simple scenarios where this may be useful
Each derived type is comparable to itself but not to other derived types.
As a slight variation, this would require deriving types to implement the interface themselves.
All derived types provide a strongly typed static default.
I've had a few scenarios in my time (including one in a pet project right now actually) where I have other bits of API that are generic that I'd like to be able to call like this.
Detailed design
Interfaces and abstract classes can use the derived type
Interfaces and abstract classes can apply constraints to derived types
Static class members can use the derived type
A concrete derived class must pass itself as the type argument
An abstract derived class or interface may pass itself or a further
: this
parameter of its own as thethis
argumentWhen not declaring implementations (for example in variables, fields) indirectly derived types may be used as arguments
This part of the proposal is possibly the weakest. I'm not sure how much value it provides, and it represents a bit of a departure from existing generics behaviour. This should probably be removed.
An extended version of the example from Lippert's blog, updated and annotated with this feature
Drawbacks
This would (I believe) require a CLR change.
Unresolved questions
Is there any way this could be made to work with non abstract virtual base classes?
Should some shorthand be provided to make defining derived classes nicer? Credit to @CodeSingularity for spawning this train of thought in the old issue.
could expand into
Should we be allowed to access implied members?
Beta Was this translation helpful? Give feedback.
All reactions