bevacqua
bevacqua

Reputation: 48566

Generic interface constraints

I'm trying to reduce the following piece of code:

public interface IQuestion<TSite, TStyling, TRelation> : IShallowPost
    where TSite : INetworkSite<TStyling, TRelation>
    where TStyling : INetworkSiteStyling
    where TRelation : INetworkSiteRelation
{

To something like this:

public interface IQuestion<TSite> : IShallowPost
    where TSite : INetworkSite<TStyling, TRelation>
    where TStyling : INetworkSiteStyling
    where TRelation : INetworkSiteRelation
{

How can I make that work?

Since having something like this:

internal sealed class Question : IQuestion<NetworkSite, NetworkSiteStyling, NetworkSiteRelation>

Makes very little sense to me compared to

internal sealed class Question : IQuestion<NetworkSite>

Update @Jared

Sure, this would be it.

/// <summary>
/// Represents a question on one of the Stack Exchange sites.
/// <para>https://api.stackexchange.com/docs/types/question</para>
/// </summary>
/// <typeparam name="TSite">The concrete type of <see cref="INetworkSite{TStyling,TRelation}"/>.</typeparam>
/// /// <typeparam name="TStyling">The concrete type of <see cref="INetworkSiteStyling"/>.</typeparam>
/// <typeparam name="TRelation">The concrete type of <see cref="INetworkSiteRelation"/>.</typeparam>
/// <typeparam name="TMigrationInfo">The concrete type of <see cref="IMigrationInfo{TSite,TStyling,TRelation}"/>.</typeparam>
/// <typeparam name="TShallowUser">The concrete type of <see cref="IShallowUser"/>.</typeparam>
/// <typeparam name="TComment">The concrete type of <see cref="IComment{TShallowUser}"/>.</typeparam>
/// <typeparam name="TAnswer">The concrete type of <see cref="IAnswer{TShallowUser,TComment}"/>.</typeparam>
public interface IQuestion<TShallowUser, TComment, TMigrationInfo, TSite, TStyling, TRelation, TAnswer> : IShallowPost<TShallowUser, TComment>
    where TShallowUser : IShallowUser
    where TComment : IComment<TShallowUser>
    where TSite : INetworkSite<TStyling, TRelation>
    where TStyling : INetworkSiteStyling
    where TRelation : INetworkSiteRelation
    where TMigrationInfo : IMigrationInfo<TSite, TStyling, TRelation>
    where TAnswer : IAnswer<TShallowUser, TComment>
{
    /// <summary>
    /// The id of this question.
    /// </summary>
    [JsonProperty("question_id")]
    long? QuestionId { get; set; }
    /// <summary>
    /// The date this question was locked.
    /// </summary>
    [JsonConverter(typeof(UnixDateTimeConverter))]
    [JsonProperty("locked_date")]
    DateTime? LockedDate { get; set; }
    /// <summary>
    /// The date this question was marked as community wiki.
    /// </summary>
    [JsonConverter(typeof(UnixDateTimeConverter))]
    [JsonProperty("community_owned_date")]
    DateTime? CommunityOwnedDate { get; set; }
    /// <summary>
    /// The amount of answers posted in response to this question.
    /// </summary>
    [JsonProperty("answer_count")]
    long? AnswerCount { get; set; }
    /// <summary>
    /// The id of the accepted answer, if any.
    /// </summary>
    [JsonProperty("accepted_answer_id")]
    long? AcceptedAnswerId { get; set; }
    /// <summary>
    /// Migration info telling where this question was migrated to.
    /// </summary>
    [JsonProperty("migrated_to")]
    TMigrationInfo MigratedTo { get; set; }
    /// <summary>
    /// Migration info telling where this question was migrated from.
    /// </summary>
    [JsonProperty("migrated_from")]
    TMigrationInfo MigratedFrom { get; set; }
    /// <summary>
    /// Date when the active bounty on this question ends.
    /// </summary>
    [JsonConverter(typeof(UnixDateTimeConverter))]
    [JsonProperty("bounty_closes_date")]
    DateTime? BountyClosesDate { get; set; }
    /// <summary>
    /// The amount of reputation spent on the bounty.
    /// </summary>
    [JsonProperty("bounty_amount ")]
    long? BountyAmount { get; set; }
    /// <summary>
    /// The date this question was closed.
    /// </summary>
    [JsonConverter(typeof(UnixDateTimeConverter))]
    [JsonProperty("closed_date")]
    DateTime? ClosedDate { get; set; }
    /// <summary>
    /// The date this question was marked as protected.
    /// </summary>
    [JsonConverter(typeof(UnixDateTimeConverter))]
    [JsonProperty("protected_date")]
    DateTime? ProtectedDate { get; set; }
    /// <summary>
    /// The title of this question.
    /// </summary>
    [JsonProperty("title")]
    string Title { get; set; }
    /// <summary>
    /// A list of tags applied on this question.
    /// </summary>
    [JsonProperty("tags")]
    IList<string> Tags { get; set; }
    /// <summary>
    /// The reason this post was closed.
    /// </summary>
    [JsonProperty("closed_reason")]
    string ClosedReason { get; set; }
    /// <summary>
    /// The amount of users who favorited this question.
    /// </summary>
    [JsonProperty("favorite_count")]
    long? FavoriteCount { get; set; }
    /// <summary>
    /// The amount of views this question had.
    /// </summary>
    [JsonProperty("view_count")]
    long? ViewCount { get; set; }
    /// <summary>
    /// A list of answers posted on this question.
    /// </summary>
    [JsonProperty("answers")]
    IList<TAnswer> Answers { get; set; }
    /// <summary>
    /// A link to the question.
    /// </summary>
    [JsonProperty("link")]
    string Link { get; set; }
    /// <summary>
    /// A boolean indicating whether this question is answered or considered unanswered.
    /// </summary>
    [JsonProperty("is_answered")]
    bool? IsAnswered { get; set; }
}

I need the concrete types instead of the interfaces because of Json deserialization, but I really hate having to pass around those huge generic type lists. It's fugly.

Upvotes: 2

Views: 766

Answers (1)

Nuffin
Nuffin

Reputation: 3972

You can make that constraint shorter if you use C# 4 or above and declare both type parameters of INetworkSite as covariant, which will make your interfaces look like this:

public interface IQuestion<TSite> : IShallowPost
    where TSite : INetworkSite<INetworkSiteStyling, INetworkSiteRelation>
{
    // interface members
}

public interface INetworkSite<out TStyling, out TRelation>
    // these constraints are not actually needed for the example to work,
    // but they seemed logical, so I left them in
    where TStyling : INetworkSiteStyling
    where TRelation : INetworkSiteRelation
{
    // interface members
}

however, if INetworkSite requires any kind of input of the given types (method parameters, ref parameters, settable properties), this will not work, thus forcing you to leave the constraint as it is.

It is, of course, possible to define only one type parameter as covariant, allowing only that one to be left out of the constraint.

Upvotes: 3

Related Questions